[dgit import tarball dovecot 1:2.4.1+dfsg1-6+deb13u7 dovecot_2.4.1+dfsg1-6+deb13u7.debian.tar.xz]
--- /dev/null
+#!/bin/sh
+
+set -eu
+
+autoreconf -f -i -v -Wall
+(cd pigeonhole && autoreconf -f -i -v -Wall)
--- /dev/null
+dovecot (1:2.4.1+dfsg1-6+deb13u7) trixie-security; urgency=medium
+
+ * Import upstream fixes for multiple security issues. (Closes: #1146018)
+ - CVE-2026-27852: DoS by sending mail with bad header
+ - CVE-2026-33263: submission-login: Panic when
+ mail_max_userip_connections is reached: Panic: epoll_ctl(del, 8)
+ failed: Bad file descriptor
+ - CVE-2026-33604: SMTP Smuggling via Missing Dot-Stuffing After Bare
+ Carriage Return
+ - CVE-2026-33605: managesieve-login: Pre-auth crash
+ - CVE-2026-33607: Dovecot IMAP LIST match_sub() Exponential
+ Backtracking — CPU Denial of Service
+ - CVE-2026-40013: pigeonhole: Stack Buffer Underflow in Pigeonhole
+ ManageSieve CHECKSCRIPT/PUTSCRIPT
+ - CVE-2026-40014: IMAP THREAD REFERENCES O(N²) CPU DoS via Crafted
+ References Header (index-thread-links.c)
+ - CVE-2026-40015: imap-hibernate can be crashed
+ - CVE-2026-40017: IMAP THREAD O(M³) CPU DoS via CRC32 Hash Collision
+ in strmap (mail-index-strmap.c / hash2.c)
+ - CVE-2026-40018: MySQL multi-byte escaping wrong
+ - CVE-2026-40204: acl: lda_mailbox_autocreate can bypass acl
+ restrictions
+ - CVE-2026-42007: Sieve editheader RCE
+ - CVE-2026-42391: imap: Pre-login memory/CPU growth with ID command
+ - CVE-2026-42394: sieve: symlink traversal flaw could result in
+ arbitrary file disclosure
+ - CVE-2026-52681: Sieve resource usage tracking lost when active
+ script changes
+ - CVE-2026-52687: IMAP: COMPRESS ZSTD can cause excessive memory usage
+ - CVE-2026-73209: imap-login crash: Self-recursion on zero-output
+ decompress chunks
+ - CVE-2026-33606: dsync: Mail content can cause dsync protocol
+ injection
+ - CVE-2026-40203: IMAP Compression Can Reveal Whether a Small Synced
+ Email Body Matches Sender-Chosen Text
+ - CVE-2026-42008: XCLIENT FORWARD= bare token not namespaced, allows
+ nopassword injection via trusted proxy
+ - CVE-2026-42392: imap-urlauth leaks memory into user-visible error
+ messages
+ - CVE-2026-42393: doveadm_password or api key length can still be
+ leaked with timing comparisons
+ - CVE-2026-42395: Single NUL-Byte XCLIENT FORWARD Payload Crashes
+ - CVE-2026-40205: OAuth2 passdb scope enforcement bypass via OR
+ semantics in remote validation path
+ - CVE-2026-73208: auth: db-oauth2: aud claim used as fallback for
+ missing scope claim
+ * lib-mail: istream-header-filter - Fix potential assert-crash
+ (Closes: #1144639)
+ * CI: Disable test-build-twice job, which is known to fail on trixie
+
+ -- Noah Meyerhans <noahm@debian.org> Wed, 16 Sep 2026 15:06:35 -0400
+
+dovecot (1:2.4.1+dfsg1-6+deb13u6) trixie-security; urgency=medium
+
+ * Security update (Closes: #1136444)
+ * [76ceed4] CVE-2026-27851: lib-var-expand: Reset safe state when
+ transfer is unset
+ * [4af6fb3] CVE-2026-40016: lib-sieve: Enforce CPU time limit within
+ :contains and :matches matcher loops
+ * [366ef61] CVE-2026-33603: login-common: Only accept base64 in sasl
+ * [26bd41e] CVE-2026-40020: IMAP folders can be shared-spammed to
+ everyone.
+ * [b6f5bac] CVE-2026-42006: imap-login: Excessive memory usage DoS
+
+ -- Noah Meyerhans <noahm@debian.org> Mon, 18 May 2026 16:03:51 -0400
+
+dovecot (1:2.4.1+dfsg1-6+deb13u5) trixie; urgency=medium
+
+ * [b357180] autopkgtests: Add managesieved authentication test
+ * [c9d69a1] Fix memory leak in CVE-2026-27857 fix
+
+ -- Noah Meyerhans <noahm@debian.org> Wed, 06 May 2026 15:18:43 -0400
+
+dovecot (1:2.4.1+dfsg1-6+deb13u4) trixie-security; urgency=medium
+
+ * [bc29057] CVE-2025-59028: auth: Don't disconnect auth client when
+ invalid base64 SASL input is received
+ * [fee7a9a] CVE-2025-59031: stop shipping the decode2text shell script
+ * [9a4442e] CVE-2025-59032: managesieve-login: Fix crash when command
+ didn't finish on the first call
+ * [2711b3e] CVE-2026-24031, CVE-2026-27860: auth: fix ldap and sql
+ injection
+ * [d30f1c3] CVE-2026-27855: fix OTP authentication reply vulnerability
+ * [e1b0ff7] CVE-2026-27856: doveadm: fix timing oracle attack
+ * [b8a69bf] CVE-2026-27857: fix resource exhaustion DoS in NOOP command
+ parsing
+ * [85dd068] CVE-2026-27858: fix pre-authentication managesieve memory
+ consumption issue
+ * [880e332] CVE-2026-27859: fix uncontrolled resource allocation when
+ delivering specially crafted email messages
+
+ -- Noah Meyerhans <noahm@debian.org> Tue, 31 Mar 2026 15:07:17 -0400
+
+dovecot (1:2.4.1+dfsg1-6+deb13u3) trixie; urgency=medium
+
+ * [1186296] import upstream fix for possible crash in ldap userdb
+ (Closes: #1121000)
+ * [14c13a5] backport upstream fix for crash in trash plugin (Closes: #1127029)
+ * [95bf3b3] Backport fix for segfault when group ACLs are present but the
+ user has no groups (Closes: #1129952)
+
+ -- Noah Meyerhans <noahm@debian.org> Fri, 06 Mar 2026 09:36:28 -0500
+
+dovecot (1:2.4.1+dfsg1-6+deb13u2) trixie; urgency=medium
+
+ * [6ac2883] Clean up a few typos in default/example config (Closes: #1112667)
+ * [7feb544] Ensure default lmtpd auth_username_format matches the global value
+ (Closes: #1111469)
+ * [216ec20] import upstream patch for improperly terminated auth_oauth2_post_setting_defines
+ (Closes: #1116328)
+ * [46eab61] lib-sieve/sieve-script.c: sieve_script_create_common: Correctly handle errors.
+ (Closes: #1116070)
+
+ -- Noah Meyerhans <noahm@debian.org> Thu, 23 Oct 2025 10:01:33 -0400
+
+dovecot (1:2.4.1+dfsg1-6+deb13u1) trixie-security; urgency=high
+
+ * Import upstream fix for an issue with authentication cache management that
+ could result in users being logged in as the wrong user in certain
+ configurations. (CVE-2025-30189) (Closes: #1115964)
+
+ -- Noah Meyerhans <noahm@debian.org> Wed, 24 Sep 2025 09:14:50 -0400
+
+dovecot (1:2.4.1+dfsg1-6) unstable; urgency=medium
+
+ * [8c6ba88] Fix LDAP SASL auth support (Closes: #1106784)
+ * [fac9131] Avoid -fstack-clash-protection on hppa architecture
+ (Closes: #1107609)
+ * [071beb5] d/control: update Uploaders to reflect the current reality
+
+ -- Noah Meyerhans <noahm@debian.org> Wed, 18 Jun 2025 10:01:58 -0400
+
+dovecot (1:2.4.1+dfsg1-5) unstable; urgency=medium
+
+ * [e6e5ef7] Fix typo in conf.d/auth-passwdfile.conf.ext (Closes: #1106072)
+ * [2a34ed0] Import upstream fix for dovecot-lda data loss (Closes: #1106485)
+ * [6c287ea] Fix typo in doveadm-fetch(1) (Closes: #1106533)
+ * [11f8b62] Fix invalid metadata in debian/patches/skip-rfc-subdir.patch
+
+ -- Noah Meyerhans <noahm@debian.org> Wed, 28 May 2025 15:45:52 -0400
+
+dovecot (1:2.4.1+dfsg1-4) unstable; urgency=medium
+
+ * [235de56] managesieved: lintian: suppress library-not-linked-against-libc
+ * [74e7dcd] Ensure we use a 32-bit time_t on i386
+ * [461bc68] dovecot-core: postinst: fix purge of obsolete ucf conf files
+ (Closes: #1105003)
+
+ -- Noah Meyerhans <noahm@debian.org> Mon, 12 May 2025 20:36:26 -0400
+
+dovecot (1:2.4.1+dfsg1-3) unstable; urgency=medium
+
+ * [bdf895a] upstream fix for GSSAPI authentication regression
+ (Closes: #1104549)
+ * [8f15017] d/copyright: Correct canonical reference to GNU licenses
+ * [ad8f661] dovecot-flatcurve: correct missing dependency on ucf
+ * [131567a] Fix miscellaneous groff errors in upstream manpages
+ * [a4a1404] Correct lintian warning debian-news-entry-has-unknown-version
+
+ -- Noah Meyerhans <noahm@debian.org> Fri, 02 May 2025 11:20:05 -0400
+
+dovecot (1:2.4.1+dfsg1-2) unstable; urgency=medium
+
+ * [7c1b5b9] dovecot-ldap: postinst: correct conffile list (Closes: #1104047)
+ * [73a254a] config: set mail_home and add mbox deprecation note
+
+ -- Noah Meyerhans <noahm@debian.org> Mon, 28 Apr 2025 15:53:54 -0400
+
+dovecot (1:2.4.1+dfsg1-1) unstable; urgency=medium
+
+ * Release 1:2.4.1+dfsg1-1 to unstable
+
+ -- Noah Meyerhans <noahm@debian.org> Thu, 24 Apr 2025 13:06:17 -0400
+
+dovecot (1:2.4.1+dfsg1-1~exp2) experimental; urgency=medium
+
+ * [e30a611] flatcurve: Set breaks+replaces relationship on dovecot-fts-flatcurve
+ (Closes: #1102937)
+
+ -- Noah Meyerhans <noahm@debian.org> Fri, 18 Apr 2025 15:34:42 -0400
+
+dovecot (1:2.4.1+dfsg1-1~exp1) experimental; urgency=medium
+
+ * [18c4170] d/watch: handle suffixes in upstream release filenames
+ * [340250d] New upstream version 2.4.1+dfsg1
+ * [2fcd382] Ensure we don't try to download files from the internet during build
+ * [4497016] refresh patches
+ * [af108c1] Remove obsolete patches
+ * [0ed2624] Fix 32-bit integer rollover in tests
+ * [528f075] d/rules: remove autogenerated sources on clean
+
+ -- Noah Meyerhans <noahm@debian.org> Sun, 30 Mar 2025 11:48:57 -0400
+
+dovecot (1:2.4.0+dfsg1-1~exp6) experimental; urgency=medium
+
+ * [827f8b7] fix and re-enable 32-bit builds
+
+ -- Noah Meyerhans <noahm@debian.org> Mon, 24 Mar 2025 15:16:57 -0400
+
+dovecot (1:2.4.0+dfsg1-1~exp5) experimental; urgency=medium
+
+ * [cea2f60] d/rules: remove additional files on clean
+ * [24e625b] ci: extend the timeout for the test-build-twice job
+ * [1df7d66] Fix ABI identification and add safety checking for it
+
+ -- Noah Meyerhans <noahm@debian.org> Mon, 17 Mar 2025 09:56:33 -0400
+
+dovecot (1:2.4.0+dfsg1-1~exp4) experimental; urgency=medium
+
+ * [0806d3b] Move 90-sieve-extprograms back to dovecot-sieve where it belongs
+ (Closes: #1100146)
+
+ -- Noah Meyerhans <noahm@debian.org> Thu, 13 Mar 2025 17:41:17 -0400
+
+dovecot (1:2.4.0+dfsg1-1~exp3) experimental; urgency=medium
+
+ * [4989934] Install a config file for flatcurve
+ * [baeb072] Add default lmtpd configuration
+ * [1d1d874] d/rules: drop some obsolete configure options
+ * [6bc7f0b] pigeonhole: enable ldap pluggin support
+ * [1998623] Enable experimental support for SMTPUTF8 and UTF8=ACCEPT
+ * [22abd27] enable cdb support
+ * [369d4a4] ci: disable i386 builds
+ * [e6ff65b] update autopkgtests
+ * [56dd0d6] debian/dovecot-core.NEWS: Document configuration changes
+ * [3d12c1e] d/rules: Update ABI detection
+
+ -- Noah Meyerhans <noahm@debian.org> Tue, 11 Mar 2025 11:31:12 -0400
+
+dovecot (1:2.4.0+dfsg1-1~exp2) experimental; urgency=medium
+
+ * [ab636e4] Correct ucf handling of /etc/dovecot/dovecot.conf
+ * [ea5102c] Stop building for 32-bit architectures
+ * [88fecb4] fix shell error in dovecot-managesieved.postinst
+ * [68875fa] Fix some groff issues in manpages
+ * [b406a2f] drop stale lintian overrides
+ * [34e4a3b] lintian: ignore circular dependency between -core and -sieve
+ * [ee13475] d/copyright: Update or remove some attributions
+
+ -- Noah Meyerhans <noahm@debian.org> Wed, 12 Feb 2025 16:39:17 -0500
+
+dovecot (1:2.4.0+dfsg1-1~exp1) experimental; urgency=medium
+
+ [ Michael Tokarev ]
+ * Update to 2.4.0
+
+ [ Noah Meyerhans ]
+ * Update default configuration from upstream example config bundle at
+ https://github.com/dovecot/tools/blob/main/dovecot-2.4.0-example-config.tar.gz
+ * Remove obsolete config files on upgrade if unmodified
+ * Update /usr/share/dovecot/protocols.d/* for 2.4
+
+ -- Noah Meyerhans <noahm@debian.org> Tue, 11 Feb 2025 12:03:31 -0500
+
+dovecot (1:2.3.21.1+dfsg1-1) unstable; urgency=medium
+
+ [ Noah Meyerhans ]
+ * [452a10b] Move systemd unit files to /usr (Closes: #1071915)
+
+ [ Niels Thykier ]
+ * [a9caf51] Avoid unnecessary implicit requirement for `(fake)root`
+
+ [ Christian Göttsche ]
+ * [8c253d1] salsa-ci: enable build_twice job
+ * [47122cd] Bump to standards version 4.7.0 (no further changes)
+ * [4062094] Replace obsolete build-dependency pkg-config with pkgconf
+ * [f1221b8] Split overlong line in changelog
+ * [dd876aa] Annotate Debian patches
+ * [590287e] Fix typos in changelog
+
+ [ Noah Meyerhans ]
+ * [a212eb8] New upstream version 2.3.21.1+dfsg1
+ - Fix CVE-2024-23184 (Closes: #1078876)
+ - Fix CVE-2024-23185 (Closes: #1078877)
+
+ -- Noah Meyerhans <noahm@debian.org> Sat, 17 Aug 2024 13:26:24 -0400
+
+dovecot (1:2.3.21+dfsg1-3) unstable; urgency=medium
+
+ * [883dc1a] Add libtirpc-dev to build-depends (Closes: #1065213)
+
+ -- Noah Meyerhans <noahm@debian.org> Sat, 09 Mar 2024 22:31:22 -0800
+
+dovecot (1:2.3.21+dfsg1-2) unstable; urgency=medium
+
+ [ Christian Göttsche ]
+ * [a2fbc2f] split-protocols.patch: patch all-settings.c to successfully build
+ twice (Closes: #1044797)
+
+ [ Noah Meyerhans ]
+ * [70e4426] Drop arm64 from libunwind builddep arch list
+
+ -- Noah Meyerhans <noahm@debian.org> Mon, 30 Oct 2023 13:40:35 -0700
+
+dovecot (1:2.3.21+dfsg1-1) unstable; urgency=medium
+
+ [ Noah Meyerhans ]
+ * [753b4fe] Don't build the unmaintained lucene fts plugin (Closes: #1040884)
+ * [5597486] New upstream version 2.3.21+dfsg1
+
+ [ Christian Göttsche ]
+ * [b8017f1] Cleanup temporary build files
+ * [35e1afe] Silence prototype conflicts
+ * [8dda8b9] Update Lintian overrides
+ * [6bae82f] Bump to standards version 4.6.2 (no further changes)
+ * [1f973d2] Mark hurd patch forwarded
+
+ -- Noah Meyerhans <noahm@debian.org> Sat, 14 Oct 2023 08:52:10 -0700
+
+dovecot (1:2.3.20+dfsg1-1) unstable; urgency=medium
+
+ [ Christian Göttsche ]
+ * [fb2a5b7] d/rules: enable stack clash protection
+ * [2666970] d/patches: bump _FORTIFY_SOURCE to level 3
+
+ [ Noah Meyerhans ]
+ * [eab5171] New upstream version 2.3.20+dfsg1
+ * [d6135a4] Drop dependency on obsolete lsb-base package
+
+ -- Noah Meyerhans <noahm@debian.org> Sun, 25 Jun 2023 16:17:56 -0700
+
+dovecot (1:2.3.19.1+dfsg1-2.1) unstable; urgency=medium
+
+ * Non-maintainer upload.
+ * [b02ebc9] Don't use deprecated crypt module.
+ (closes: #1028513)
+
+ -- Bas Couwenberg <sebastic@debian.org> Fri, 20 Jan 2023 07:01:26 +0100
+
+dovecot (1:2.3.19.1+dfsg1-2) unstable; urgency=medium
+
+ [ Christian Göttsche ]
+ * [281fb2c] d/patches: cherry-pick fix for CVE-2022-30550 (Closes: #1016351)
+ * [9c58e71] d/patches: fix uninitialized read in doveadm-oldstats
+ * [a76a24d] d/control: bump to standards version 4.6.1 (no further changes)
+ * [4aaaa8b] Update Lintian overrides
+
+ -- Noah Meyerhans <noahm@debian.org> Fri, 29 Jul 2022 19:58:28 -0700
+
+dovecot (1:2.3.19.1+dfsg1-1) unstable; urgency=medium
+
+ [ Christian Göttsche ]
+ * [e40f93f] d/patches: avoid usage of PATH_MAX not available on hurd
+ * [19e00cd] d/rules: enable backtrace generation
+ * [5bf1c43] d/patches: debug flaky unit test
+
+ [ Noah Meyerhans ]
+ * [b73422f] New upstream version 2.3.19.1+dfsg1
+ * [c88bfc0] Update changelog for 1:2.3.19.1+dfsg1-1 release
+ * [ca59548] Update lintian overrides
+ * [d6406c2] d/copyright: update declarations for current maintainers
+
+ -- Noah Meyerhans <noahm@debian.org> Wed, 22 Jun 2022 09:27:01 -0700
+
+dovecot (1:2.3.19+dfsg1-1) unstable; urgency=medium
+
+ [ Christian Göttsche ]
+ * [0d29e45] d/rules: enable LTO via DEB_BUILD_MAINT_OPTIONS instead of custom flags
+ * [560cceb] d/source/lintian-overrides: update very-long-line-length-in-source-file overrides
+ * [b99d09e] d/copyright: update years
+ * [9ee8271] d/dovecot-core.prerm: drop as superseded by debhelper
+ * [907f85c] d/maintscripts: update
+ * [2b38240] d/dovecot-core.postinst: drop support for version skips
+ * [dcb76d1] d/dovecot-core.postinst: only link certs if existent (Closes: #1009872)
+ * [d223bbd] d/patches: add patch to support openssl 3.0 (Closes: #996273)
+
+ [ Noah Meyerhans ]
+ * [9f3175e] New upstream version 2.3.19+dfsg1
+
+ -- Noah Meyerhans <noahm@debian.org> Sun, 05 Jun 2022 18:29:18 +0000
+
+dovecot (1:2.3.18+dfsg1-1) unstable; urgency=medium
+
+ [ Noah Meyerhans ]
+ * [36966c8] New upstream version 2.3.18+dfsg1
+ * [042bda4] Refresh patches for 1:2.3.18+dfsg1-1
+
+ -- "Noah Meyerhans" <noahm@debian.org> Thu, 10 Feb 2022 20:05:50 +0000
+
+dovecot (1:2.3.17.1+dfsg1-1) unstable; urgency=medium
+
+ [ Christian Göttsche ]
+ * [40b0010] New upstream version 2.3.17+dfsg1
+ * [3c377e0] New upstream version 2.3.17.1+dfsg1
+ * [e2f1ce2] d/patches: rebase and drop upstream applied ones
+ * [533b7ad] d/control: bump to standards version 4.6.0 (no further changes)
+ * [02ed6cf] debian: reduce Lintian issues
+ * [bb3ae48] d/salsa-ci.yml: skip cross build and do not fail on Lintian
+ warnings
+ * [bcda7e4] d/control: build against Lua 5.4
+ * [9eed0dd] d/control: enable libunwind support on available archs
+ * [1990699] d/patches: cherry-pick memory leak commit
+ * [426df46] d/patches: cherry-pick imapsieve fix
+ * [e3d0747] d/patches: add patch for LTO by avoiding unaligned access
+ (Closes: #997513)
+
+ -- Noah Meyerhans <noahm@debian.org> Tue, 14 Dec 2021 09:24:23 -0800
+
+dovecot (1:2.3.16+dfsg1-3) unstable; urgency=medium
+
+ * [7b858b6] Fix FTBFS on mips(64)el. Stacktrace generation on these
+ architectures requires -funwind-tables, as with 32-bit arm.
+
+ -- Noah Meyerhans <noahm@debian.org> Thu, 16 Sep 2021 08:41:27 -0700
+
+dovecot (1:2.3.16+dfsg1-2) unstable; urgency=medium
+
+ [ Christian Göttsche ]
+ * [e1e9ece] d/patches: rework backtrace test patch
+ * [be404bf] d/patches: add big-endian patch
+
+ -- Noah Meyerhans <noahm@debian.org> Fri, 10 Sep 2021 16:10:50 -0700
+
+dovecot (1:2.3.16+dfsg1-1) unstable; urgency=medium
+
+ [ Christian Göttsche ]
+ * [ff4a227] New upstream version 2.3.14+dfsg1
+ * [963fa3b] New upstream version 2.3.15+dfsg1 (Closes: #991323, #983510)
+ * [5e0c898] d/watch: adjust dversionmangle for dfsg suffix
+ * [9ffb0f5] d/patches: update
+ * [850e1d6] New upstream version 2.3.16+dfsg1
+ * [7140b87] d/patches: rebase patches
+ * [fb1b77e] d/rules: enable LTO
+ * [ce7055d] d/control: add libsystemd-dev dependency
+ * [db93263] d/copyright: drop unused section
+ * [aeec1e8] d/rules: update how to set systemdsystemunitdir
+ * [ebe9709] d/patches: resolve compiler warnings
+ * [19b2bb0] d/changelog: bump to 1:2.3.16+dfsg1-1
+ * [58a4078] d/patches: update 32bit warnings patch
+
+ [ Noah Meyerhans ]
+ * [f217c2e] Fix indexer crash
+ * [b075317] Import upstream patch for indexer crash on client disconnect
+ * [36e8740] drop debian/dovecot-core.maintscript
+
+ -- Noah Meyerhans <noahm@debian.org> Thu, 02 Sep 2021 13:22:16 -0700
+
+dovecot (1:2.3.13+dfsg1-2) unstable; urgency=high
+
+ * Import upstream fixes for security issues (Closes: #990566):
+ - CVE-2021-29157: Path traversal issue allowing an attacker with
+ access to the local filesystem can trick OAuth2 authentication into
+ using an HS256 validation key from an attacker-controlled location
+ - CVE-2021-33515: Sensitive information could be redirected to an
+ attacker-controlled address because of a STARTTLS command injection
+ bug in the submission service
+
+ -- Noah Meyerhans <noahm@debian.org> Tue, 20 Jul 2021 08:05:19 -0700
+
+dovecot (1:2.3.13+dfsg1-1) unstable; urgency=medium
+
+ [ Christian Göttsche ]
+ * [6829237] New upstream version 2.3.13 (Closes: #979363)
+ - CVE-2020-24386: IMAP hibernation allows accessing other peoples mail
+ - CVE-2020-25275: MIME parsing crashes with particular messages
+
+ * [6d25736] Add libzstd-dev to build-dependencies (Closes: #969165)
+ * [5956798] Rebase patches
+ * [2cb63c3] Bump to standards version 4.5.1 (no further changes)
+ * [548bac5] Drop unmatched copyright src/lib-ntlm/* wildcard
+ * [6f33f3f] Ignore package-contains-documentation-outside-usr-share-doc
+ false-positives
+ * [dde9c94] Handle removed configuration file in postinst
+
+ [ Pino Toscano ]
+ * [04a60e3] d/{control,rules}: disable apparmor support on !linux archs
+ (Closes: #951869)
+
+ [ Helmut Grohne ]
+ * [e5f9fcb] d/patches: improve cross-compile support (Closes: #979370)
+
+ -- Noah Meyerhans <noahm@debian.org> Mon, 25 Jan 2021 15:38:17 -0800
+
+dovecot (1:2.3.11.3+dfsg1-2) unstable; urgency=medium
+
+ [ Christian Göttsche ]
+ * [44770f6] Add patch for 32bit compiler warnings
+ * [053865a] Lintian: remove unused override
+ * [4ece2e1] Lintian: add forwarded header to Debian specific patches
+ * [67872b7] Lintian: ignore Debian only man page
+ * [d30bd7e] Lintian: tag manpage-without-executable got renamed to
+ spare-manual-page
+ * [3bdf952] Limit libcap-dev build-dependency to linux-any
+ * [28f6425] Drop acute accent in man page
+ * [8c15850] Add patch allowing GSSAPI containing NULL
+
+ -- Noah Meyerhans <noahm@debian.org> Wed, 19 Aug 2020 12:06:07 -0700
+
+dovecot (1:2.3.11.3+dfsg1-1) unstable; urgency=high
+
+ * New upstream release fixes security issues (Closes: #968302)
+ - CVE-2020-12100 - Receiving mail with deeply nested MIME parts leads to
+ resource exhaustion as Dovecot attempts to parse it.
+ - CVE-2020-12673 - Dovecot's NTLM implementation does not correctly check
+ message buffer size, which leads to reading past allocation which can
+ lead to crash.
+ - CVE-2020-12674 - Dovecot's RPA mechanism implementation accepts
+ zero-length message, which leads to assert-crash later on.
+ * Add libcap-dev to build-dependencies to support dropping linux
+ capabilities.
+
+ -- Noah Meyerhans <noahm@debian.org> Thu, 13 Aug 2020 16:21:24 -0700
+
+dovecot (1:2.3.10.1+dfsg1-2) unstable; urgency=medium
+
+ * Support sd_notify with systemd (Closes: #951722)
+ * Add necessary CFLAGS and LDFLAGS settings to ensure functional backtrace
+ generation. (Closes: #962630)
+ * Suppress additional library-not-linked-against-libc lintian warnings some
+ plugins as false-positives, observed on armel systems
+
+ [ Andreas Hasenack ]
+ * d/t/control, d/t/testmails: cherry-pick updated autopkgtests from
+ Ubuntu's 1:2.2.35-2ubuntu1:
+ - d/t/testmails: dropped the hardcoded "Ubuntu" name from the banner
+ text and made it distribution agnostic
+ - d/t/control: added lsb-release to test dependencies, used to get the
+ distribution name
+
+ -- Noah Meyerhans <noahm@debian.org> Tue, 16 Jun 2020 08:29:02 -0700
+
+dovecot (1:2.3.10.1+dfsg1-1) unstable; urgency=medium
+
+ * New upstream release addresses multiple security issues
+ - CVE-2020-10957
+ - CVE-2020-10958
+ - CVE-2020-10967
+ (Closes: #960963, #930919, #928492)
+ * Refresh patches
+ * Strip non-DFSG-compliant docs from .orig archives
+ * Incorporate a number of improvements to debian/ metadata contributed by
+ Christian Göttsche <cgzones@googlemail.com>
+ * Move pid file to /run (Closes: #925443)
+ * Add noahm@debian.org to Uploaders
+ * Work around flakiness in autopkgtest suite
+ * Suppress library-not-linked-against-libc lintian warnings some plugins as
+ false-positives
+
+ -- Noah Meyerhans <noahm@debian.org> Wed, 10 Jun 2020 10:41:37 -0700
+
+dovecot (1:2.3.7.2-1) unstable; urgency=medium
+
+ * [dcaf24e] New upstream version 2.3.7.2
+ - Fixes CVE-2019-11500 for dovecot-core
+ * [111beef] Update pigeonhole to 0.5.7.2
+ - Fixes CVE-2019-11500 for pigeonhole/managesieve
+ * [a422c4c] Bump Standards-Version to 4.4.0; no changes needed
+ * [56e37ed] Bump dh compat to 12; no changes needed.
+ - Drop d/compat in favor debhelper-compat B-D.
+ * [476edbd] Refresh dovecot_name.patch and ssl-cert-location.patch
+ * [9dc7904] Drop patches included in 2.3.7.2.
+ - CVE-2019-10691
+ - CVE-2019-11494
+ - CVE-2019-11499
+ - CVE-2019-7524
+ - avoid-double-closing-mysql.patch
+ - lib-master-test-event-stats-Use-PRIu64-format.patch
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Thu, 29 Aug 2019 11:55:51 +0300
+
+dovecot (1:2.3.4.1-5) unstable; urgency=medium
+
+ * [bd00402] Fix CVE-2019-11494 and CVE-2019-11499 (Closes: #928235)
+ - submission-login: fix null pointer dereference when client
+ disconnects during authentication (CVE-2019-11494)
+ - submission-login: fix assert-crash when receiving an invalid
+ authentication message over TLS (CVE-2019-11499)
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Mon, 29 Apr 2019 23:35:05 +0300
+
+dovecot (1:2.3.4.1-4) unstable; urgency=high
+
+ * [d04d4ba] Fix assert-crash in JSON encoder (CVE-2019-10691)
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Thu, 18 Apr 2019 10:21:19 +0300
+
+dovecot (1:2.3.4.1-3) unstable; urgency=high
+
+ * [07c9212] Fix two buffer overflows when reading oversized FTS headers
+ and/or oversized POP3-UIDL headers (CVE-2019-7524).
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Mon, 25 Mar 2019 23:06:01 +0200
+
+dovecot (1:2.3.4.1-2) unstable; urgency=medium
+
+ [ Laurent Bigonville ]
+ * [ac99918] Fix double-free crash in mysql driver
+ Fix double closing of the connection in the mysql driver, this should
+ fix the crash in the dovecot auth process, taken from upstream.
+ (Closes: #918339)
+
+ [ Apollon Oikonomopoulos ]
+ * [8a30446] Bump Standards-Version to 4.3.0; no changes needed
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Thu, 14 Mar 2019 11:02:39 +0200
+
+dovecot (1:2.3.4.1-1) unstable; urgency=high
+
+ * [bebf0b4] New upstream version 2.3.4.1
+ + Fixes CVE-2019-3814: TLS client auth username handling
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Tue, 05 Feb 2019 16:19:12 +0200
+
+dovecot (1:2.3.4-2) unstable; urgency=medium
+
+ * [51d1317] Fix FTBFS on 32-bit platforms.
+ Cherry-pick upstream commit de42b54, fixing the event-stats test on
+ 32-bit platforms.
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Sat, 24 Nov 2018 02:02:17 +0200
+
+dovecot (1:2.3.4-1) unstable; urgency=medium
+
+ * [14c247f] New upstream version 2.3.4
+ * [7fed004] Update pigeonhole to 0.5.4
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Fri, 23 Nov 2018 22:00:06 +0200
+
+dovecot (1:2.3.3-1) unstable; urgency=medium
+
+ [ Jelmer Vernooij ]
+ * Trim trailing whitespace.
+
+ [ Apollon Oikonomopoulos ]
+ * [6591a99] New upstream version 2.3.3
+ * [3d718ec] Bump Standards-Version to 4.2.1; no changes needed
+ * [123bd32] Update pigeonhole to 0.5.3
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Thu, 04 Oct 2018 17:29:40 +0300
+
+dovecot (1:2.3.2.1-1) unstable; urgency=medium
+
+ * [40ba9f0] New upstream bugfix release 2.3.2.1
+ * [87045ac] Drop fix-ftbfs-on-32bit.patch; merged upstream
+ * [5bb22a4] Bump Standards-Version to 4.1.5; no changes needed
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Tue, 10 Jul 2018 17:51:43 +0300
+
+dovecot (1:2.3.2-2) unstable; urgency=medium
+
+ * [48067de] Fix FTBFS on 32-bit platforms by cherry-picking upstream commit
+ 1e23986f.
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Wed, 04 Jul 2018 12:43:14 +0300
+
+dovecot (1:2.3.2-1) unstable; urgency=medium
+
+ * [bb03669] New upstream version 2.3.2
+ + Upload to unstable
+ * [d29da3a] Merge 2.3 package changes from experimental. Important changes:
+ + [b3d1e17] Enable AppArmor support, see
+ https://wiki2.dovecot.org/Plugins/Apparmor
+ • B-D on libapparmor-dev
+ + [c0c55bd] Enable Lua scripting support for authdb/passdb.
+ • B-D on liblua5.3-dev
+ • New binary package, dovecot-auth-lua
+ + [4f6792e] Build with sodium support, enabling the ARGON2I and ARGON2ID
+ password schemes.
+ • B-D on libsodium-dev
+ + [54347e7] Build with ICU support enabling FTS unicode normalization
+ • B-D on libicu-dev
+ + New dovecot-submissiond binary package for the dovecot submission agent;
+ see https://wiki2.dovecot.org/Submission.
+ * [4db4813] Change maintainer address to dovecot@packages.d.o
+ * [5118354] Update pigeonhole to 0.5.2
+ * [52a7af4] Drop murmur3-big-endian.patch; merged upstream
+ * [22a6eee] Refresh dovecot_name.patch
+ * [3af7568] dovecot_name.patch: apply to submissiond as well
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Wed, 04 Jul 2018 08:57:45 +0300
+
+dovecot (1:2.2.36-1) unstable; urgency=medium
+
+ * [19f2274] d/gbp.conf: set merge-mode to "merge" to preserve pigeonhole/
+ when importing new dovecot sources
+ * [6b9bf0d] New upstream version 2.2.36
+ * [be12f22] Bump pigeonhole version to 0.4.24
+ + Remove new file under doc/rfc
+ + Ship the new imap_filter_sieve module in dovecot-sieve
+ * [b77be59] Bump Standards-Version to 4.1.4; no changes needed
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Wed, 06 Jun 2018 09:31:49 +0300
+
+dovecot (1:2.2.35-2) unstable; urgency=medium
+
+ * [7665652] Use git-subtree to generate pigeonhole patch from git; add
+ single-debian-patch to d/source/local-options
+ * [bfa0f10] d/rules: specify libdir manually; previous upload moved modules
+ under /usr/lib/<triplet>, which was bound to break existing setups
+ * [982e826] d/copyright: adjust pigeonhole path and bump years
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Thu, 22 Mar 2018 16:56:40 +0200
+
+dovecot (1:2.2.35-1) unstable; urgency=medium
+
+ * [8108cba] New upstream version 2.2.35
+ * [6cbbaa1] Update pigeonhole to 0.4.23 (Closes: #892137)
+ * [9ace5f2] Switch Vcs-* URLs to salsa.d.o
+ * [ef40625] d/rules: call configure via dh_auto_configure.
+ Thanks to Helmut Grohne (Closes: #885854)
+ * [a459455] Drop B-D on libcurl4-gnutls-dev; removed upstream since 2.2
+ * [235af9d] Update upstream signing key
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Tue, 20 Mar 2018 11:15:42 +0200
+
+dovecot (1:2.2.34-2) unstable; urgency=high
+
+ * [868dc65] Update pigeonhole to 0.4.22
+ * Set urgency to high due to the security fixes in 2.2.34-1
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Fri, 02 Mar 2018 18:36:23 +0200
+
+dovecot (1:2.2.34-1) unstable; urgency=medium
+
+ * [f53dc9a] New upstream version 2.2.34 (Closes: #921529)
+ Fixes the following security issues:
+ + CVE-2017-15130: TLS SNI config lookups may lead to excessive memory
+ usage (Closes: #891820)
+ + CVE-2017-14461: rfc822_parse_domain information leak vulnerability
+ (Closes: #891819)
+ + CVE-2017-15132: auth client leaks memory if SASL authentication is
+ aborted (Closes: #888432)
+ * [0dc98c6] Do not patch all-settings.c; regenerate it at build time
+ instead. Thanks to Aki Tuomi!
+ * [e678e3b] Bump dh compat to 11
+ + B-D on debhelper (>= 11~)
+ + Use dh_installsystemd instead of dh_systemd_enable
+ * [271b290] Bump Standards-Version to 4.1.3; no changes needed
+ * [3cd6715] d/copyright: bump upstream and debian years
+ * [380d1ac] Drop the ENABLED flag from /etc/default/dovecot (but let the
+ initscript handle it if it exists)
+ * [97d6fae] d/watch: switch upstream URL to https://
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Thu, 01 Mar 2018 10:55:49 +0200
+
+dovecot (1:2.2.33.2-1) unstable; urgency=medium
+
+ * [8216f38] New upstream version 2.2.33.2
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Sat, 11 Nov 2017 20:59:43 +0200
+
+dovecot (1:2.2.33.1-1) unstable; urgency=medium
+
+ * [dbd1132] New upstream version 2.2.33.1
+ + [b3d1f2d] Refresh split-protocols.patch
+ + [e0de123] Update pigeonhole to 0.4.21
+ * [ef6a1eb] Set mail_privileged_group to 'mail' by default (Closes: #711856)
+ * [aeb6cf3] d/copyright: convert to Format 1.0
+ * [5961f9d] Use dh-autoreconf for both, dovecot and pigeonhole.
+ * [85f1f0f] Bump Standards to 4.1.1; no changes needed
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Fri, 13 Oct 2017 16:28:14 +0300
+
+dovecot (1:2.2.32-2) unstable; urgency=medium
+
+ * [fa71c69] dovecot-core.postinst: remove dovecot-common's postrm
+ (Closes: #696382)
+ * [e835c67] Ship decode2text.sh as an example (Closes: #767313)
+ * [63fb486] Deprecate dovecot-dbg in favor of auto dbgsyms
+ * [36b44b9] Handle unsupported SSLv2/SSLv3 in the ssl_protocols setting
+ gracefully (Closes: #866752)
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Tue, 19 Sep 2017 16:59:38 +0300
+
+dovecot (1:2.2.32-1) unstable; urgency=medium
+
+ * [6652d9c] New upstream version 2.2.32.
+ * [c9cb096] Update pigeonhole to 0.4.20.
+ * [b499950] dovecot-core: remove SSL key/cert symlinks on purge
+ (Closes: #867157)
+ * [dbdcc66] dovecot-core.postinst: ignore adduser errors (Closes: #867849)
+ * [476c950] Bump Standards to 4.1.0; no changes needed.
+ * [2914efa] Drop B-D on autotools-dev, it is depended on by debhelper 10.
+ * [305d022] Remove Fabio, Joel and Marco from Uploaders. Thanks for your work!
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Tue, 12 Sep 2017 16:15:52 +0300
+
+dovecot (1:2.2.31-1) unstable; urgency=medium
+
+ * [9b058f3] New upstream version 2.2.31
+ + [2b577c1] Bump pigeonhole version to 0.4.19
+ * Enable TLS by default:
+ + [7ca4b1c] Update SSL cert location patch; cert/key should reside under
+ /etc/dovecot/private by default.
+ + [05d3d0f] Use ssl-cert-snakeoil certificates to setup SSL by default
+ (Closes: #376146, #786570)
+ + [862901f] dovecot-core.postinst: manage 10-ssl.conf using ucf
+ (Closes: #850538)
+ + [418df05] README.Debian: document the new TLS setup
+ + [47bade9] dovecot-core.NEWS: document TLS support
+ * [8356bc0] Handle /etc/dovecot/private mode using dpkg-statoverride
+ * dovecot-core.postinst: cleanup
+ + [afbd33f] dovecot-core.postinst: always call adduser
+ + [ee22dc5] dovecot-core.postinst: remove obsolete conffile handling
+ + [7bb298b] dovecot-core.postinst: do not remove the imapd user/group
+ * [815a2d1] README.Debian: cleanup
+ * [91115a3] Use noawait dpkg triggers (lintian warning)
+ * [e845cec] Add basic usage DEP-8 test, doing end-to-end tests involving
+ LDA, IMAP and POP3.
+ * [71c73ef] systemd: convert service to Type=simple and start after
+ network-online.target (Closes: #865546, #825562)
+ * [1534fac] dovecot.service: enable ProtectSystem=full
+ * [d276c69] B-D only on default-libmysqlclient-dev
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Tue, 27 Jun 2017 18:18:12 +0300
+
+dovecot (1:2.2.30.2-1) unstable; urgency=medium
+
+ * [401e83d] New upstream version 2.2.30.2
+ * [1ea8321] Bump pigeonhole version to 0.4.18
+ * [97bf8ec] Drop CVE-2017-2669 patch
+ * [9c1bbe2] Drop fix-sha3-on-big-endian.patch
+ * [d3c607b] Refresh dovecot_name.patch
+ * [5c64268] Bump Standards to 4.0.0; no changes needed
+ * [0b884fc] Bump compat to 10
+ + B-D on debhelper (>= 10)
+ + Drop B-D on dh-systemd, now provided by debhelper
+ + Run dh --without=autoreconf, since we use autotools-dev
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Thu, 22 Jun 2017 22:22:59 +0300
+
+dovecot (1:2.2.27-3) unstable; urgency=high
+
+ * [117285a] Remove /etc/dovecot/README (Closes: #849290)
+ * [04e8ce3] auth: Do not double-expand key in passdb dict when
+ authenticating (CVE-2017-2669) (Closes: #860049)
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Tue, 11 Apr 2017 00:46:54 +0300
+
+dovecot (1:2.2.27-2) unstable; urgency=medium
+
+ * [30586e3] Fix SHA3 on big-endian architectures.
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Thu, 15 Dec 2016 22:24:56 +0200
+
+dovecot (1:2.2.27-1) unstable; urgency=medium
+
+ [ Jaldhar H. Vyas ]
+ * [b1e4693] Imported Upstream version 2.2.27
+ + Includes fix for CVE-2016-8652 (Closes: #846605)
+
+ [ Apollon Oikonomopoulos ]
+ * [b25993a] Drop patches merged upstream:
+ + call_openssl_cleanup_at_deinit.patch
+ + disable_sslv23.patch
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Wed, 14 Dec 2016 21:48:46 +0200
+
+dovecot (1:2.2.26.0-4) unstable; urgency=medium
+
+ * [3015f35] Drop references to SSLv2 in the default SSL protocols (Closes: #844271)
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Mon, 14 Nov 2016 17:55:26 +0200
+
+dovecot (1:2.2.26.0-3) unstable; urgency=medium
+
+ * [b03027b] Call OPENSSL_cleanup() on dcrypt_openssl unload. Fixes FTBFS
+ with OpenSSL 1.1.0c.
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Sun, 13 Nov 2016 10:56:30 +0200
+
+dovecot (1:2.2.26.0-2) unstable; urgency=medium
+
+ * [9db7d1b] Fix upgrades from 2.2.25 (Closes: #843028)
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Tue, 08 Nov 2016 15:06:16 +0200
+
+dovecot (1:2.2.26.0-1) unstable; urgency=medium
+
+ [ Apollon Oikonomopoulos ]
+ * [18fc181] New upstream version 2.2.26.0 (Closes: #828286, #834837)
+ * [3ecfd3c] Update pigeonhole to 0.4.16
+ * [61ff825] Move libdovecot-ldap and libdict_ldap to dovecot-ldap (Closes:
+ #830135).
+ * [b3a1650] Ubuntu: disable -Bsymbolic-functions ld flag.
+ Thanks to Christian Ehrhardt <christian.ehrhardt@canonical.com>
+ (Closes: #842151) (LP: #1636781)
+ * [5828ab1] B-D on default-libmysqlclient-dev (but keep plain
+ libmysqlclient-dev as an alternative to ease backports).
+ * [0086110] Drop DRAC plugin.
+ Thanks to Christian Ehrhardt <christian.ehrhardt@canonical.com>
+ (Closes: #842153)
+
+ [ Jaldhar H. Vyas ]
+ * [60808eb] Move aclocal *.m4 files into -dev package.
+ * [52fd869] Move lib95_imap_sieve_plugin.so into dovecot-sieve (Closes:
+ #832046).
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Thu, 07 Jul 2016 10:17:58 +0200
+
+dovecot (1:2.2.25-1) unstable; urgency=medium
+
+ * [cc29a81] Imported Upstream version 2.2.25
+ * [d19bcca] Updated pigeonhole patch to 0.4.14
+ * [16db179] Merged in some features of the Ubuntu dovecot package.
+ + dovecot-core: added lsb-base dependency.
+ + dovecot-core: Added apport hook.
+ + dovecot-imapd,dovecot-pop3d: Added ufw profiles.
+ Thanks to Christian Erhardt <christian.ehrhardt@canonical.com>
+ (Closes: #828864)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 01 Jul 2016 17:07:03 -0400
+
+dovecot (1:2.2.24-1) unstable; urgency=medium
+
+ * [26020b6] Imported Upstream version 2.2.24 (Closes: #818652)
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Mon, 09 May 2016 10:42:08 +0300
+
+dovecot (1:2.2.23-1) unstable; urgency=medium
+
+ [ Jaldhar H. Vyas ]
+ * Drop missing-expunges.patch, merged upstream
+
+ [ Apollon Oikonomopoulos ]
+ * [8a01915] Imported Upstream version 2.2.23
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Tue, 12 Apr 2016 17:30:03 +0300
+
+dovecot (1:2.2.22-1) unstable; urgency=medium
+
+ [ Jaldhar H. Vyas ]
+ * [2321581] Imported Upstream version 2.2.22
+ * [3fa8a62] Updated pigeonhole patch to 0.4.13
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 18 Mar 2016 19:18:34 -0400
+
+dovecot (1:2.2.21-1) unstable; urgency=medium
+
+ [ Jaldhar H. Vyas ]
+ * [d9c0630] Imported Upstream version 2.2.21 (Closes: #809666, #708539,
+ #801346, 803223)
+ * [5360548] Updated pigeonhole patch to 0.4.12
+ * [5e6783f] Fixed typo in dovecot-core.README.Debian.
+ Thanks to Ingo Wichmann <iw-debian@linuxhotel.de> (Closes: #809717)
+ * [d00d0c7] Create /var/lib/dovecot in the package. (Closes: #801752)
+ * [6510373] Upstream patch for sync problem which could cause expunged
+ messages to keep reappearing. (Closes: #684499)
+ * [040f7fa] dovecot-core: dovecot.socket not enabled on installation
+ (Closes: #814999)
+ * [fc29003] /etc/dovecot/10-ssl.conf no longer managed by ucf or modified by
+ postinst. Thanks to Santiago Vila <sanvila@unex.es> (Closes: #773237)
+ * [0d16e16] Fixed some lintian warnings.
+ * [801ba7e] Added Apollon to uploaders
+
+ [ Apollon Oikonomopoulos ]
+ * [99ef3d8] Build with lz4 support (Closes: #784321)
+ * [92f68aa] Fix nss userdb (Closes: #712764)
+ * [41b9bde] Disable dovecot.socket in existing installations.
+ * [49c5b97] d/rules: specify systemd unit dir manually (Closes: #720854)
+ * [a377ccb] Convert to dh sequencer
+ * [48af954] B-D on debhelper >= 9
+ * [591c315] Bump standards to 3.9.7; no changes needed
+ * [ceb629e] Use dh_installinit --name
+ * [ef8d8ac] d/rules: refactor file installation
+ * [a899bcc] dovecot-core: use dh_installman
+ * [6159e00] d/rules: build in parallel if requested
+ * [51014e6] d/control: use HTTPS Vcs-* URLs
+ * [b385cf5] dovecot-sieve: replace Conflicts with Breaks
+ * [b4a9e68] Add basic DEP-8 tests
+ * [7452649] Add DEP-8 tests for systemd support
+ * [e5101aa] Re-enable PIE and bindnow
+ * [5717808] Fix invoke-rc.d calls and never call init.d directly
+ * [3504241] Drop debconf remains
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Tue, 01 Mar 2016 19:31:42 -0500
+
+dovecot (1:2.2.20-1) UNRELEASED; urgency=medium
+
+ * [68d5038] Imported Upstream version 2.2.20
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 13 Dec 2015 10:21:21 -0500
+
+dovecot (1:2.2.19-1) UNRELEASED; urgency=medium
+
+ * [146ef57] Imported Upstream version 2.2.19
+ * [3af7cad] Updated pigeonhole patch to 0.4.9
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 13 Dec 2015 09:41:56 -0500
+
+dovecot (1:2.2.18-2) unstable; urgency=high
+
+ * [3f3bf71] Updated pigeonhole patch to 0.4.8 (Closes: #792669)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 23 Aug 2015 23:16:28 -0400
+
+dovecot (1:2.2.18-1) unstable; urgency=medium
+
+ * [cce20a5] Imported Upstream version 2.2.18. Closes: #786760
+ * [36d2ec1] Refresh patch dovecot_name.patch.
+ * [109e6f8] Drop patch cve-2015-3420.patch: applied upstream.
+ * [6c59f09] Depend on krb5-multidev rather than libkrb5-dev.
+
+ -- Jelmer Vernooij <jelmer@debian.org> Sun, 24 May 2015 15:01:19 +0000
+
+dovecot (1:2.2.16-1) unstable; urgency=medium
+
+ * [e9d9193] Imported Upstream version 2.2.16
+ * [976c256] Remove gbp- prefix from section names in debian/gbp.conf.
+ * [762b9a6] Add Dutch translation. Thanks, Frans Spiesschaert. Closes: #766203
+ * [dea3dd6] Drop bye_logout_not_sent.patch: already included upstream.
+
+ -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 May 2015 12:23:05 +0000
+
+dovecot (1:2.2.13-12) unstable; urgency=high
+
+ * [48f6fe4] Add patch cve-2015-3420.patch: Fix SSL/TLS handshake failures
+ leading to a crash of the login process with newer versions of OpenSSL.
+ Closes: #783649 (CVE-2015-3420)
+
+ -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 May 2015 11:38:30 +0000
+
+dovecot (1:2.2.13-11) unstable; urgency=high
+
+ * [ebc0377] Don't allow install of dovecot-sieve without a new enough
+ dovecot-core. (Closes: #772885)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 14 Dec 2014 12:27:50 -0500
+
+dovecot (1:2.2.13-10) unstable; urgency=high
+
+ [ Jelmer Vernooij ]
+ * [93db7f0] Fix path to 90-sieve-extprograms.conf in dovecot-sieve.postinst.
+ Closes: #772703, #772711
+
+ [ Jaldhar H. Vyas ]
+ * [8c627a4] Add another db_stop this time for triggers. Die #770695 die!
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 10 Dec 2014 19:56:20 -0500
+
+dovecot (1:2.2.13-9) unstable; urgency=high
+
+ * [5b689f6] Made some overlooked configuration files into conffiles;
+ deleted excess files from /usr/share/doc/dovecot-core.
+ * [83f2fc4] Explicitly stop debconf in dovecot-core postinst which hopefully
+ fixes the last of the install hangs.
+ Thanks to Chris Gilbert <zeke.gilbert@gmail.com> (Closes: #770695)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Tue, 09 Dec 2014 06:17:23 -0500
+
+dovecot (1:2.2.13-8) unstable; urgency=medium
+
+ * [b2f652f] Turn off SSL by default and leave SSL cert locations commented
+ out. Hopefully this will be a satisfactory lowest common denominator for
+ new installs without messing with upgrades.
+ (Closes: #771334, #771407)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Tue, 02 Dec 2014 00:21:30 -0500
+
+dovecot (1:2.2.13-7) unstable; urgency=high
+
+ * [daea09a] Commented out cert locations so install doesn't bomb if the
+ certs haven't been created yet.
+ (Closes: #706216, #732263, #767154, #768253, #769461, #770697)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 28 Nov 2014 00:27:03 -0500
+
+dovecot (1:2.2.13-6) unstable; urgency=medium
+
+ * [f7205c0] dovecot-dev: removed dependency on dovecot-core
+ * [e393868] Removed SSL certificate generation from postinst. From now on
+ you have to do this yourself. See dovecot-core's README.debian for
+ instructions. (Closes: #730828)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 25 Oct 2014 23:31:42 -0400
+
+dovecot (1:2.2.13-5) unstable; urgency=medium
+
+ * Fix name of organizationName field in SSL configuration for self-
+ signed certs. Closes: #760653
+
+ -- Jelmer Vernooij <jelmer@debian.org> Sat, 06 Sep 2014 23:19:51 +0200
+
+dovecot (1:2.2.13-4) unstable; urgency=medium
+
+ * Add Provides with dovecot ABI version, for plugins to depend on.
+ Closes: #456021
+
+ -- Jelmer Vernooij <jelmer@debian.org> Sun, 20 Jul 2014 19:31:09 +0200
+
+dovecot (1:2.2.13-3) unstable; urgency=medium
+
+ * Build-depend on clucene 2.3 or later, which upstream lists as the
+ minimum version. Closes: #754141
+ * Use configuration file for openssl certificate configuration.
+ * Use canonical address for Vcs-Browser header.
+ * Remove unnecessary asterisk from NEWS entry; fixes lintian warning.
+ * Remove unused lintian override for usr/lib/dovecot/imap.
+ * dovecot-core: Ignore lintian warnings for empty directories.
+
+ -- Jelmer Vernooij <jelmer@debian.org> Tue, 08 Jul 2014 01:48:08 +0200
+
+dovecot (1:2.2.13-2) unstable; urgency=medium
+
+ * [bd5e34b] Patches from upstreams' hg repo to fix BYE and LOGOUT not being
+ sent. (Closes: #751682)
+ * [452e336] Czech translation for debconf.
+ Thanks to Michal Šimůnek (Closes: #751389)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 25 Jun 2014 01:38:59 -0400
+
+dovecot (1:2.2.13-1) unstable; urgency=medium
+
+ * [0680040] Imported Upstream version 2.2.13
+
+ -- Jelmer Vernooij <jelmer@debian.org> Sun, 25 May 2014 18:45:38 +0200
+
+dovecot (1:2.2.13~rc1-1) unstable; urgency=medium
+
+ * [7751508] Imported Upstream version 2.2.13~rc1
+ + Fixes denial of service vulnerability (CVE-2014-3430). Closes: #747549
+
+ -- Jelmer Vernooij <jelmer@debian.org> Sat, 10 May 2014 14:34:14 +0200
+
+dovecot (1:2.2.12-3) unstable; urgency=medium
+
+ * [0383db0] Break long line.
+ * [c961b6a] Fix installation of sieve plugins. Closes: #742770, #684271
+ * [388d2bf] Strip body.rfc5173.txt from the pigeonhole patch, as it is
+ non-free. Closes: #745398
+
+ -- Jelmer Vernooij <jelmer@debian.org> Wed, 23 Apr 2014 02:56:56 +0200
+
+dovecot (1:2.2.12-2) unstable; urgency=medium
+
+ * [c882a38] Add build dependencies libstemmer-dev and libexttextcat-dev,
+ used by dovecot-lucene.
+ * [67762d4] Use autotools-dev to update config.guess and config.sub.
+ * [33e79b7] Update Japenese po file. Thanks, victory. Closes: #730171
+ * [cbf213b] Add non-standard-dir-perm override for /etc/dovecot/private.
+
+ -- Jelmer Vernooij <jelmer@debian.org> Sun, 23 Mar 2014 17:57:04 +0000
+
+dovecot (1:2.2.12-1) experimental; urgency=medium
+
+ * [52b67de] Update watch file for 2.2 series.
+ * [c4eac1f] Verify upstream tarball signature in watch file.
+ * [3a81ff9] Imported Upstream version 2.2.12
+ * [8950a86] Bump standards version to 3.9.5 (no changes).
+ * [cd82417] Add myself to uploaders.
+
+ -- Jelmer Vernooij <jelmer@debian.org> Thu, 06 Mar 2014 22:45:19 +0000
+
+dovecot (1:2.2.10-1) unstable; urgency=low
+
+ * [0496c52] Imported Upstream version 2.2.10
+ * [515dd61] Added new package for Lucene full text search support.
+ Thanks to Jelmer Vernooij <jelmer@debian.org> for the patch.
+ (Closes: #685979)
+ * [718a68e] Fix old private key location in README.Debian.
+ Thanks to Jelmer Vernooij <jelmer@debian.org> for the patch.
+ (Closes: #702385)
+ * [42da568] dovecot-solr: Make sure solr-schema.xml gets installed.
+ (In /usr/share/dovecot)
+ (Closes: #695185)
+ * [77acbf7] Added /usr/share/dovecot/dovecot-abi file in dovecot-dev to
+ document -- you guessed it! -- the dovecot ABI version.
+ * [feb597a] Added support for xz compression.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 06 Mar 2014 02:51:34 -0500
+
+dovecot (1:2.2.9-1) unstable; urgency=low
+
+ [ Jaldhar H. Vyas ]
+ * [77468cf] Imported Upstream version 2.2.9
+ * [43e08f3] Place dovenull user in its own group. (Closes: #725164)
+ * [e1a3e9c] Handled the fact that dovecot-db.conf.ext is no longer used.
+ (Closes: #728107, #730403)
+
+ [Debconf translation updates]
+ * Russian (Yuri Kozlov). (Closes: #729106)
+ * German (Chris Leick). (Closes: #729358)
+ * Danish (Joe Hansen). (Closes: #729425)
+ * French (Julien Patriarca). (Closes: #729966)
+ * Portuguese (Américo Monteiro). (Closes: #730006)
+ * Polish (Michał Kułach). (Closes: #730061)
+ * Italian (Beatrice Torracca). (Closes: #730136)
+ * Japanese (victory). (Closes: #73017)
+ * Swedish (Martin Bagge / brother). (Closes: #730188)
+ * Spanish; (Camaleón). (Closes: #730354)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 16 Jan 2014 15:42:13 -0500
+
+dovecot (1:2.2.8-1) UNRELEASED; urgency=low
+
+ * [6157a2b] New upstream version 2.2.8
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 21 Nov 2013 16:54:46 -0500
+
+dovecot (1:2.2.5-1) experimental; urgency=low
+ [ Micah Anderson ]
+ * [a0035bf] New upstream version 2.2.5
+ * [a053c49] Update pigeonhole patch to 0.4.1
+ * [689cd67] refreshed patches
+
+ [ Jaldhar H. Vyas ]
+ * Caused bugs and then fixed them again.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 09 Sep 2013 00:57:32 -0400
+
+dovecot (1:2.1.17-2) unstable; urgency=low
+
+ * [e8286e0] New version of drac patch taken from Ubuntu which works better
+ with 2.x (Closes: #716764)
+ * [23acb40] Add a patch from Ubuntu to report the distro name in the login
+ banner why not.
+ * [f8d566e] Don't need dovecot-common package anymore; get rid of it.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 07 Sep 2013 14:58:05 -0400
+
+dovecot (1:2.1.17-1) experimental; urgency=low
+
+ [ Jaldhar H. Vyas ]
+ * [fa0d6aa] Re-enable mbox write locking patch to comply with policy 11.6
+ (Closes: #720502)
+ * [38691fb] New upstream version (Closes: #719021)
+ * [1361144] prompts in dovecot-core postinst debconfiscated.
+ * IN MEMORIAM: Goldy the Goldfish (2000-2013) You were a prince (or
+ perhaps princess?) among fish and we shall all miss you dearly.
+ May your karmas merit much punya in future lives.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 23 Aug 2013 01:46:20 -0400
+
+dovecot (1:2.1.16-1) experimental; urgency=low
+
+ * [9741bd8] New Upstream version
+ * [3476489] Updated pigeonhole patch to 0.3.5
+ * [d4f236f] Removed some patches which are no longer required.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 14 Jun 2013 16:25:19 -0400
+
+dovecot (1:2.1.7-8) experimental; urgency=low
+
+ * This version is not actually intended for upload. It merely undoes
+ some changes made for the the wheezy release. Namely, the following
+ features are back:
+ - TCP Wrappers support
+ - Hurd compatibility support
+ - Triggers.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 13 Jun 2013 16:14:21 -0400
+
+dovecot (1:2.1.7-7) unstable; urgency=high
+
+ * If you are upgrading from stable or earlier versions of this package
+ from testing/unstable please carefully read
+ /usr/share/doc/dovecot-core/README.Debian.gz for important information
+ about changes.
+ * [0d74b31] Move Breaks/Replaces mailavenger from dovecot-common to
+ dovecot-core (Closes: #694376)
+ * [a8030a1] Revamped dovecot-cores README.Debian by adding any info I could
+ think of in order to ease upgrade problems. (Closes: #696820)
+ * [04798d3] Don't touch 10-ssl.conf at all. Eventually I will DTRT with
+ regards to the default generated ssl certificates but in the mean time
+ this will do the least mischief. (Closes: #696817)
+ * [fde17d1] Patch to make /etc/dovecot/readme point to the right place for
+ the example configuration. (Closes: #698941)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 04 Feb 2013 16:27:17 -0500
+
+dovecot (1:2.1.7-6) unstable; urgency=high
+
+ * WARNING: in order to get this package into wheezy some functionality
+ from the previous release had to be removed. Namely:
+ - TCP Wrappers support
+ - Hurd compatibility support
+ - Triggers
+ All this will be coming back in the next version but for now, if you need
+ any of it, stick with -5.
+ * [1f869e0] NEWS.Debian was not getting added to the package (Closes: #693621)
+ * [564c5e2] Add breaks and replaces mailavenger for debian-common
+ (Closes: #694376)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 14 Dec 2012 17:01:33 -0500
+
+dovecot (1:2.1.7-5) unstable; urgency=high
+
+ * [132bc3b] Remove call to ntp-wait in init script. Dovecot handles a
+ skewed clock much better now. (Closes: #693225)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 14 Nov 2012 14:45:29 -0500
+
+dovecot (1:2.1.7-4) unstable; urgency=high
+
+ * [68ef7ad] piuparts complained /etc/dovecot/private was left unowned on
+ purge which is against policy (Closes: #692944)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 11 Nov 2012 23:45:07 -0500
+
+dovecot (1:2.1.7-3) unstable; urgency=high
+
+ [ Jaldhar H. Vyas ]
+ * [03d5499] Do not generate new dovecot cert if key or cert is already
+ present (Closes: #685896,#631257) Thanks to Alexander Ufimtsev
+ <alexu@ucd.ie> and Jörg-Volker Peetz <jvpeetz@web.de> for patches.
+ * [5be6c2a] You should be able to upgrade and remove dovecot-managesieved
+ without errors (Closes: #665487)
+ * [1a9ad5c] Fixes instances where UTF-8 was misused instead of mUTF-7
+ (Closes: #680035)
+ * [c0ac3ba] Backport of fix for failure to autocreate mailboxes
+ (Closes: #623440) This also requires setting the default mail_location.
+ * [ba1d3f5] Fix FTBFS on Hurd (Closes: #686931) via upstream patch backported
+ from mercurial.
+ * [cfa92b5] Upgrade pigeonhole page to 0.3.1 (Closes: #688407)
+ * [58f01c2] Generated certificates will now be created in /etc/dovecot
+ (Closes: #608719)
+ * [8e40ea5] Patch to build with PIE and bindnow
+ by intrigeri@debian.org (Closes: #679017)
+ * [87d982b] Use libwrap (Closes: #685850)
+ * [ebb5421] Start after nslcd (Closes: #692632)
+ * [2db5c1a] Add support for dpkg triggers. This means dovecot will not be
+ repeatedly restarted when installing or removing lots of plugins
+ (Closes: #601744)
+ * [bc66629] Patch to document -k in dsync.1
+ by Luca Capello <luca@pca.it> (Closes: #680992)
+ * [ba67766] Add lintian overrides.
+
+ [ Marco Nenciarini ]
+ * [ffba408] Updated watch file
+ * [9777434] Install missing default configuration files from upstream
+ * [9496828] Backport fix for segfault in managesieve triggered by
+ CHECKSCRIPT command. (Closes: #688197)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 10 Nov 2012 03:50:30 -0500
+
+dovecot (1:2.1.7-2) unstable; urgency=low
+
+ * [e23a136] Just a quick upload to make dovecot buildable on non-linux
+ platforms again. (Closes: #676817) I did it by removing the systemd
+ build-dep altogether. sd-daemon.[ch] is included in the source so if
+ systemd is installed on a system, it should be detected and socket
+ activation should happen. That is, assuming I've got everything right
+ (and documented) which is something I'll be looking into for the next
+ version.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 21 Jun 2012 23:54:47 -0400
+
+dovecot (1:2.1.7-1) unstable; urgency=low
+
+ * [7668742] Imported upstream 2.1.7 (Closes: #663243)
+ + doveadm.1 documents the move command. (Closes: #641750)
+ * [4db927a] Patch to enable systemd support. (Closes: #672266)
+ Thanks Riku Voipio.
+ * [e17ac86] Previous attempt at setting hardening flags did not include the
+ drac plugin. Simon Ruderich provided an extra patch.
+ (Closes: #653530)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 30 May 2012 16:27:21 -0400
+
+dovecot (1:2.1.4-1) experimental; urgency=low
+
+ * [6cc1e7d] Imported upstream 2.1.4
+
+ -- Micah Anderson <micah@debian.org> Tue, 10 Apr 2012 11:33:05 -0400
+
+dovecot (1:2.1.3-1) experimental; urgency=low
+
+ * [49ecc33] Imported upstream 2.1.3
+
+ -- Micah Anderson <micah@debian.org> Fri, 16 Mar 2012 22:04:46 -0400
+
+dovecot (1:2.1.2-1) experimental; urgency=low
+
+ * [70c3f6e] Imported upstream 2.1.2
+ * Add missing dependency on dpkg-dev 1.16.1
+
+ -- Micah Anderson <micah@debian.org> Thu, 15 Mar 2012 20:26:35 -0400
+
+dovecot (1:2.1.1-1) experimental; urgency=low
+
+ * [e113636] Imported upstream 2.1.1
+ * Updated pigeonhole patch to 0.3.0, supporting 2.1
+
+ -- Micah Anderson <micah@debian.org> Tue, 13 Mar 2012 23:08:06 -0400
+
+dovecot (1:2.0.18-1) unstable; urgency=low
+
+ * [85ae320] Imported Upstream version 2.0.18
+ * [9cfd1da] Upped standards version to 3.9.3
+ * [afb4164] Patch to dovecot-core/postinst so that permissions of symlinked
+ certificates aren't modified. (Closes: #646508)
+ Thanks Michael Kuhn.
+ * [bf642ee] Patch to enable hardened build flags. (Closes: #653530)
+ Thanks Moritz Muehlenhoff.
+ * [00b0d0c] Updated pigeonhole to 0.2.6
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 09 Mar 2012 00:55:13 -0500
+
+dovecot (1:2.0.15-1) unstable; urgency=low
+
+ * [a22575a] New upstream version 2.0.15: (Closes: #642045)
+ + doveadm altmove: Added -r parameter to move mails back to primary
+ storage.
+ - v2.0.14: Index reading could have eaten a lot of memory in some
+ situations
+ - doveadm index no longer affects future caching decisions
+ - mbox: Fixed crash during mail delivery when mailbox didn't yet have
+ GUID assigned to it.
+ - zlib+mbox: Fetching last message from compressed mailboxes crashed.
+ - lib-sql: Fixed load balancing and error
+ * [8ce5abc] Update pigeonhole to release 0.2.4
+ * [87658d2] Add dovecot-solr to dovecot-core's Suggests line.
+
+ -- Marco Nenciarini <mnencia@debian.org> Mon, 19 Sep 2011 19:26:48 +0200
+
+dovecot (1:2.0.14-3) unstable; urgency=low
+
+ * [f37a9ec] Enable solr full text search plugin. (LP: #620959)
+ * [cdd8b84] Build dovecot-common as architecture-all package.
+ * [af90444] Fix mail_plugin_dir default value in conf.d/10-mail.conf
+ (Closes: #624294)
+ * [61347cb] Bump debhelper build-depends to (>= 7.2.3~) because we use
+ dh_bugfiles.
+ * [4b757b6] Fix wrong configuration files path in sieve manpages.
+
+ -- Marco Nenciarini <mnencia@debian.org> Fri, 16 Sep 2011 02:26:21 +0200
+
+dovecot (1:2.0.14-2) unstable; urgency=low
+
+ [ Marco Nenciarini ]
+ * [a6896d6] Rename dovecot-common to dovecot-core. (Closes: #624835)
+ * [c36ca96] Manage protocols during package configuration and
+ deconfiguration phases.
+ * [599b55b] Update pigeonhole to release 0.2.3.
+ * [c654db0] Add a bug-script which will append doveconf -n output to bug
+ reports.
+ * [5270323] Add notes about upgrading from 1.2 to README.Debian.
+ (Closes: #635351)
+ * [5cd77a3] Add build-arch and build-indep target to debian/rules as
+ required by lintian.
+ * [be05d2a] Avoid ucf question when upgrading from squeeze and the only
+ difference in dovecot.conf is at the "protocols" line.
+ * [3c73782] Remove ucf backups during purge.
+ * [3ccf508] Make /etc/dovecot/*.ext readable by dovecot group members.
+ (Closes: #639005)
+ * [665bcc5] Use -c ${CONF} when calling doveconf to determine PIDBASE.
+ Thanks to Jonathan Hall (Closes: #627794)
+ * [325042c] Purge obsolete unmodified config files from /etc.
+ (Closes: #629397)
+ * [a9b9afb] Modified init script to report failures. (Closes: #629654)
+ * [d4622fd] Make dovecot-core suggesting all other packages containing
+ additional features.
+
+ -- Marco Nenciarini <mnencia@debian.org> Thu, 15 Sep 2011 19:43:55 +0200
+
+dovecot (1:2.0.14-1) unstable; urgency=low
+
+ * [50a947e] New upstream version 2.0.14: (Closes: #640143)
+ + doveadm: Added support for running mail commands by proxying to
+ another doveadm server.
+ + Added "doveadm proxy list" and "doveadm proxy kick" commands to
+ list/kick proxy connections (via a new "ipc" service).
+ + Added "doveadm director move" to assign user from one server to
+ another, killing any existing connections.
+ + Added "doveadm director ring status" command.
+ + userdb extra fields can now return name+=value to append to an
+ existing name, e.g. "mail_plugins+= quota".
+ - script-login attempted an unnecessary config lookup, which usually
+ failed with "Permission denied".
+ - lmtp: Fixed parsing quoted strings with spaces as local-part for
+ MAIL FROM and RCPT TO.
+ - imap: FETCH BODY[HEADER.FIELDS (..)] may have crashed or not
+ returned all data sometimes.
+ - ldap: Fixed random assert-crashing with with sasl_bind=yes.
+ - Fixes to handling mail chroots
+ - Fixed renaming mailboxes under different parent with FS layout when
+ using separate ALT, INDEX or CONTROL paths.
+ - zlib: Fixed reading concatenated .gz files.
+ * [0297425] Use xz compression for dbg packages.
+ * [4464ccf] Support parallel builds in debian/rules
+ * [19667f0] Acknowledge NMU 1:2.0.13-1.1.
+ Thanks to Luk Claes
+
+ -- Marco Nenciarini <mnencia@debian.org> Tue, 06 Sep 2011 23:18:46 +0200
+
+dovecot (1:2.0.13-1.1) unstable; urgency=low
+
+ * Non-maintainer upload.
+ * Don't ship .la files (Closes: #621297).
+
+ -- Luk Claes <luk@debian.org> Sat, 18 Jun 2011 12:31:28 +0200
+
+dovecot (1:2.0.13-1) unstable; urgency=high
+
+ [ Marco Nenciarini ]
+ * [8af9e4d] New upstream version 2.0.13:
+ + Added "doveadm index" command to add unindexed messages into
+ index/cache. If full text search is enabled, it also adds unindexed
+ messages to the fts database.
+ + added "doveadm director dump" command.
+ + pop3: Added support for showing messages in "POP3 order", which can
+ be different from IMAP message order. This can be useful for
+ migrations from other servers. Implemented it for Maildir as 'O'
+ field in dovecot-uidlist.
+ - doveconf: Fixed a wrong "subsection has ssl=yes" warning.
+ - mdbox purge: Fixed wrong warning about corrupted extrefs.
+ - sdbox: INBOX GUID changed when INBOX was autocreated, leading to
+ trouble with dsync.
+ - script-login binary wasn't actually dropping privileges to the
+ user/group/chroot specified by its service settings.
+ - Fixed potential crashes and other problems when parsing header names
+ that contained NUL characters. (CVE-2011-1929)
+ (Closes: #627443)
+
+ -- Marco Nenciarini <mnencia@debian.org> Sat, 21 May 2011 23:58:06 +0200
+
+dovecot (1:2.0.12-1) unstable; urgency=low
+
+ [ Jaldhar H. Vyas ]
+ * [66cbb94] New upstream major release. Imported Upstream version 2.0.12
+ * [a48c7fd] Updated standards version
+
+ [ Marco Nenciarini ]
+ * [5c6b334] Updated watch file
+ * [0ba683e] Updated init script to new configuration scheme. (Closes: #593527)
+ * [8370be4] Fix upgrading from rename-it.nl packages
+ * [ae804e3] Update changelog
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 25 Apr 2011 17:03:26 -0400
+
+dovecot (1:2.0.11-1) experimental; urgency=low
+
+ * [a4764a4] New upstream major release (Closes: #573889, #580929,
+ #597476, #603951, #606089, #606646, #606649, #608075, #612369, #610919)
+ * [461f667] Updated copyright and package descriptions. Thanks Stephan
+ Bosch.
+ * [7fbd92a] Drop all patches but drac support. Update pigeonhole to
+ release 0.2.1
+ * [05a5a1e] Update debian/rules to work with dovecot 2.0. Thanks
+ Stephan Bosch.
+ * [a36193e] Update debian/dovecot-common.{postinst,postrm} to handle
+ the new config file layout. Thanks Stephan Bosch.
+ * [3589afd] Remove debian/dovecot.1 manpage because is provided by
+ upstream.
+ * [f1703e1] Remove debian/dovecotpw.1 manpage as there is no dovecotpw
+ executable in dovecot 2.0. Same feature is provided through 'doveadm
+ pw' command.
+ * [e30283d] Remove expire-tool.sh wrapper as there is no expire-tool
+ in dovecot 2.0. Same feature is provided through 'doveadm expunge'
+ command.
+ * [3385061] Remove daily cron script and the related configuration
+ from the default file.
+ * [bec19a0] Update debian/dovecot-common.NEWS.Debian with upgrading
+ instructions
+ * [1e56f1c] Call dh_makeshlibs with --no-script option to avoid
+ {postinst,postrm}-has-useless-call-to-ldconfig lintian errors.
+ * [325f055] Disable numbers in patches (requires git-buildpackage >=0.5.13)
+ * [74d6ebe] Added debian/source/local-options with "unapply-patches".
+ * [0edef17] Set default protocols value as empty and enable each
+ protocol in its own configuration file in
+ /usr/share/dovecot/protocols.d (LP: #661453)
+ * [e88860c] Removed code about upgrading from pre-etch packages.
+ * [95c5d47] Removed obsolete Build-Conflict (Closes: #614053)
+ * [41dddf4] Move configurations for imapd and pop3d to the appropriate
+ packages. (Closes: #606648)
+ * [b8b66dc] Removed obsolete README.Debian and NEWS.Debian from
+ dovecot-pop3d and dovecot-imapd packages
+ * [0bda8ac] Added packages dovecot-lmtpd and dovecot-managesieved.
+ * [9a954f1] Build databases, ldap and gssapi support as plugins
+ * [1a3f323] Deregister obsolete configuration files from ucf in
+ dovecot-common postinst on upgrades.
+ * [fae4b76] Added packages dovecot-pgsql, dovecot-mysql, dovecot-sqlite,
+ dovecot-ldap, dovecot-gssapi and dovecot-sieve (Closes: #251433)
+ * [aa8983d] Imported Upstream version 2.0.11
+
+ -- Marco Nenciarini <mnencia@debian.org> Mon, 07 Mar 2011 18:23:59 +0100
+
+dovecot (1:1.2.15-3) unstable; urgency=low
+
+ * [096c373] Fix file overwrite error when upgrading from lenny
+ (Closes: #601980)
+ * [37b3220] Add --no-create-home to adduser invocation in dovecot-
+ common.postinst script (Closes: #601767)
+ * [b00b527] Fall back to `hostname` if `hostname -f` invocation fails
+ (Closes: #562780)
+ * [9102fec] Make self-generated certificates key length 2048 bits.
+
+ -- Marco Nenciarini <mnencia@debian.org> Tue, 02 Nov 2010 11:12:53 +0100
+
+dovecot (1:1.2.15-2) unstable; urgency=low
+
+ * [a0744a5] Switch vcs fields to git
+ * [d8a1bd4] Add debian/gbp.conf to make easy the usage of git-
+ buildpackage
+ * [c0dc21f] Warn that the generated SSL certificate will expire in 10
+ years instead of 365 days, as it's the lifetime of of self-generated
+ certificates since 1:1.2.13-1
+ * [6472d63] Switch to gbp-pq patch management.
+ * [bf2fc17] Upgraded sieve plugin to version 0.1.18
+ * [6b9809d] Upgraded managesieve plugin to version 0.11.12
+ * [d67cd17] Upgraded dovecot-managesieve.patch to version 0.11.12
+
+ -- Marco Nenciarini <mnencia@debian.org> Thu, 28 Oct 2010 22:50:19 +0200
+
+dovecot (1:1.2.15-1) unstable; urgency=high
+
+ [ Marco Nenciarini ]
+ * New upstream release (Closes: #587036,#597529)
+ * Updated policy version to 3.9.1.0 (no changes needed)
+
+ [ Jaldhar H. Vyas ]
+ * [SECURITY] Fixes two bugs with acls which could have allowed a user to
+ gain improper access or admin rights to shared mailboxes.
+ (Closes: #599521)
+ * Warn that the generated SSL certificate will expire in 365 days. Thanks
+ Phillip Weis. (Closes: #576455)
+ * Wherever the path to sendmail is given as /usr/lib/sendmail, change to
+ /usr/sbin/sendmail. (Closes: #570814,#595671)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 08 Oct 2010 17:34:19 -0400
+
+dovecot (1:1.2.13-1) unstable; urgency=low
+
+ [ Jaldhar H. Vyas ]
+ * dovecot-common: Upped expiration date of self-generated certificates to
+ 10 years from 1 year. (Closes: #587371)
+
+ [ Marco Nenciarini ]
+ * New upstream release:
+ - Fixed iconv() crash when it was processing several kilobytes of
+ broken continuous input. This mainly could have caused a problem
+ with IMAP SEARCH. Possibly also with some Sieve checks.
+ - If MIME encoded-words contained line feeds, Dovecot logged
+ cache corruption errors.
+ - mbox: Renaming mailbox under newly created dir didn't move index
+ directory.
+ - mbox: When generating envelope to From_-line, don't append a second
+ @owndomain if username already has one.
+ * Updated policy version to 3.9.0.0 (no changes needed)
+
+ -- Marco Nenciarini <mnencia@debian.org> Mon, 26 Jul 2010 17:03:38 +0200
+
+dovecot (1:1.2.12-1) unstable; urgency=low
+
+ * New upstream release
+ * Upgraded sieve plugin to version 0.1.17
+ * Refreshed dovecot-managesieve.patch
+ * debian/patches/sieve-fix-imapflags.patch: Removed. Fixed upstream.
+
+ -- Marco Nenciarini <mnencia@debian.org> Sat, 26 Jun 2010 18:14:15 +0200
+
+dovecot (1:1.2.11-1) unstable; urgency=high
+
+ * New upstream release
+ * urgency set to high, because under particular circumstances you could
+ cause a DoS by sending a message with a huge header.
+ * sieve: Fix addflags command in deprecated imapflags extension
+ (Closes: #570058)
+ * Include a daily expire script with a setting in /etc/default/dovecot
+ to enable it. The new script is disabled by default as it requires
+ some configurations by the system administrator. Thanks to Hans Spaans
+ * Mark expire-tool.sh as a bash script
+ * Bump Standards-Version to 3.8.4, no changes needed
+
+ -- Marco Nenciarini <mnencia@debian.org> Mon, 08 Mar 2010 22:25:46 +0100
+
+dovecot (1:1.2.10-1) unstable; urgency=low
+
+ * New upstream release (Closes: #566796)
+ * Upgraded sieve plugin to version 0.1.15
+ * Upgraded managesieve plugin to version 0.11.11
+ * Upgraded dovecot-managesieve.patch to version 0.11.11
+
+ -- Marco Nenciarini <mnencia@debian.org> Mon, 25 Jan 2010 17:21:09 +0100
+
+dovecot (1:1.2.9-2) unstable; urgency=low
+
+ * Upgraded sieve plugin to version 0.1.14
+ * Upgraded managesieve plugin to version 0.11.10
+ * Upgraded dovecot-managesieve.patch to version 0.11.10
+ * debian/patches/dovecot-example.patch: Refreshed.
+ * Added expire-tool wrapper script (Closes: #565538)
+ * Added ${misc:Depends} to the dependencies of each binary package,
+ to make lintian happy (debhelper-but-no-misc-depends)
+
+ -- Marco Nenciarini <mnencia@debian.org> Sun, 17 Jan 2010 09:39:42 +0100
+
+dovecot (1:1.2.9-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/patches/gold-fix.patch: Removed. Fixed upstream.
+
+ -- Marco Nenciarini <mnencia@debian.org> Thu, 17 Dec 2009 10:52:53 +0100
+
+dovecot (1:1.2.8-2) unstable; urgency=low
+
+ * Moved libexec to lib corrections in dovecot-managesieve.patch and
+ dovecot-managesieve-dist.patch to dovecot-example.patch
+ * debian/patches/dovecot-mboxlocking.patch: Regenerated to avoid FTBFS
+ when quilt isn't installed.
+ * debian/patches/quota-mountpoint.patch: Removed. Not needed anymore.
+ * debian/patches/dovecot-quota.patch: Removed. Quotas aren't properly
+ enabled unless mail_plugins = quota imap_quota.
+ * debian/patches/gold-fix.patch: Fixed configure script to build even
+ with binutils-gold or --no-add-needed linker flag (Closes: #554306)
+ * debian/dovecot-common.init: fixed LSB headers. Thanks to Pascal Volk.
+ (Closes: #558040)
+ * debian/changelog: added CVE references to previous changelog entry.
+ * debian/rules: checked up the build system. It's not fragile anymore.
+ (Closes: 493803)
+ * debian/dovecot-common.postinst: Now invoking dpkg-reconfigure
+ on dovecot-common is enough to generate new certificates
+ if the previous ones were removed. (Closes: #545582)
+ * debian/rules: No longer install convert-tool in /usr/bin.
+ It isn't an user utility and it should stay in /usr/lib/dovecot
+ like all other similar tool.
+
+ -- Marco Nenciarini <mnencia@debian.org> Tue, 08 Dec 2009 12:24:04 +0100
+
+dovecot (1:1.2.8-1) unstable; urgency=high
+
+ [ Marco Nenciarini ]
+ * New upstream release. (Closes: #557601)
+ * [SECURITY] Fixes local information disclosure and denial of service.
+ (see: http://www.dovecot.org/list/dovecot-news/2009-November/000143.html
+ and CVE-2009-3897)
+ * Added myself to uploaders.
+ * Switched to the new source format "3.0 (quilt)":
+ - removed dpatch from build-depends
+ - removed debian/README.source because now we use only standard
+ dpkg features
+ - regenerated all patches
+ * Prepared to switch to multi-origin source:
+ - recreated dovecot-libsieve.patch and dovecot-managesieve-dist.patch
+ starting from the upstream tarball
+ - removed all autotools related build-depends and build-conflict
+ - renamed dovecot-libsieve and dovecot-managesieve directories
+ to libsieve and managesieve.
+ * debian/rules: Moved the configuration of libsieve and managesieve from
+ the build phase to the configuration phase
+
+ [ Jaldhar H. Vyas ]
+ * Added dovecot-dbg package with debugging symbols. Thanks Stephan Bosch.
+ (Closes: #554710)
+ * Fixed some stray libexec'isms in the default configuration.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 23 Nov 2009 17:04:14 -0500
+
+dovecot (1:1.2.7-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/dovecot-common.init:
+ - use $CONF when starting the daemon. (Closes: #549944)
+ - always output start/stop messages. (Closes: #523810)
+
+ -- Fabio Tranchitella <kobold@debian.org> Sat, 14 Nov 2009 11:28:33 +0100
+
+dovecot (1:1.2.6-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/patches/v1.2.5-exec-mail_fix.dpatch: removed, merged upstream.
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 12 Oct 2009 06:56:55 +0000
+
+dovecot (1:1.2.5-2) unstable; urgency=low
+
+ * debian/dpatches/v1.2.5-exec-mail_fix.dpatch: added.
+ (Closes: #546694, #546695)
+ * debian/dovecot-common.dirs: removed /etc, /etc/ssl/{private,certs}, they
+ are handled by openssl; this should fix the piuparts test.
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 27 Sep 2009 20:53:52 +0200
+
+dovecot (1:1.2.5-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 14 Sep 2009 14:34:06 +0200
+
+dovecot (1:1.2.4-2) unstable; urgency=low
+
+ * debian/patches/dovecot-libsieve.dpatch: updated to 0.1.12.
+ (Closes: #539527)
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 02 Sep 2009 07:28:23 +0200
+
+dovecot (1:1.2.4-1) unstable; urgency=low
+
+ * New upstream release. (Closes: #537186, #540058)
+ * Bumped Standards-Version to 3.8.3, no changes needed.
+ * debian/control: only suggests ntp. (Closes: #542152)
+ * debian/patches/dovecot-managesieve-dist.dpatch: updated to the 0.11.9
+ release. (Closes: #539527)
+ * debian/rules: dovecot should be started with sequence number 20.
+ (Closes: #543473)
+
+ -- Fabio Tranchitella <kobold@debian.org> Sat, 29 Aug 2009 17:27:59 +0200
+
+dovecot (1:1.2.2-2) unstable; urgency=high
+
+ * Er that should be fcntl not fnctl. (Closes: #539474, #539486)
+ * For the sake of completeness, fixed some errors in the example config file.
+ (They are in the !include statements which are commented out by default
+ so it is unlikely that most users will actually be affected.)
+ (Closes: #539391)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 01 Aug 2009 11:38:03 -0400
+
+dovecot (1:1.2.2-1) unstable; urgency=low
+
+ [Joel Johnson]
+ * New upstream version, updated ManageSieve patch
+ - fixes index corruption condition (Closes: #537388)
+ * Set default for mbox_write_locks to "fnctl dotlock" according to
+ Debian Policy (Closes: #537326)
+ * Set MySQL build dependency to use version-agnostic -dev package
+ * Drop postgres-configure patch, functionality included upstream
+ * Updated dovecot-libsieve to version 0.1.9
+ - fixes subaddress matching (Closes: #537386)
+ - check additional address-list headers (Closes: #537379)
+
+ [Jaldhar H. Vyas]
+ * Remove unneeded build-dependencies on byacc and flex now that we no longer
+ use cmusieve.
+
+ -- Joel Johnson <mrjoel@lixil.net> Tue, 28 Jul 2009 20:35:57 -0600
+
+dovecot (1:1.2.1-2) unstable; urgency=low
+
+ * Update to use default automake (Closes: #536880) now that current upstream
+ successfully builds with 1.10 (was previously a problem in bug 473754.
+ * Updated Standards-Version to 3.8.2 (no action required)
+ * Remove ./var/run directories to fix lintian warnings.
+ * Updated dovecot-libsieve patch to version 0.1.8, removed autogen.sh,
+ tests, and doc/rfc
+ * Updated dovecot-managesieve patch to version 0.11.7
+ * Updated dovecot-managesieve-dist patch to Mercurial revision 12b9733ee8b0
+ (0.11.7+), removed lib-cmusieve, doc/rfs, autogen.sh
+ * Add additional upgrading note to README.Debian to clarify the ManageSieve
+ configuration changes needed - existing configurations break if not
+ updated! (Closes: #537158)
+ * Include the ChangeLog and README for sieve and ManageSieve
+
+ -- Joel Johnson <mrjoel@lixil.net> Wed, 15 Jul 2009 20:29:33 -0600
+
+dovecot (1:1.2.1-1) unstable; urgency=low
+
+ [ Joel Johnson ]
+ * New upstream release
+ * Update packaging for 1.2 release
+ - update SIEVE patch to use Dovecot rewrite (version 0.1.7,
+ removed RFCs and tests for a DFSG patch)
+ - update ManageSieve patch and software to hg revision 2c9b4b4ab6a8
+ (0.11.6+) for 1.2.1 support
+ + removed lib-cmusieve since building against newer sieve
+ + removed doc/rfc for DFSG compliance
+ + removed autogen.sh since we run autotools during the build
+ - updated other misc patches
+
+ -- Joel Johnson <mrjoel@lixil.net> Sun, 12 Jul 2009 11:15:01 -0600
+
+dovecot (1:1.1.16-1) unstable; urgency=low
+
+ * New upstream release. (Closes: #531599)
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 04 Jun 2009 12:38:59 +0200
+
+dovecot (1:1.1.15-1) unstable; urgency=low
+
+ * New upstream release; sorry, no time to include extra upstream patches
+ which are not released, feel free to submit them as dpatch files and I will
+ add them to the package. (Closes: #529923)
+ * debian/control: updated Standards-Version, no changes needed.
+ * debian/dovecot-common.init: patched to support ENABLED=0 in
+ /etc/default/dovecot, useful if dovecot is used as local-only IMAP server
+ through PREAUTH interface. (Closes: #524302)
+ * debian/dovecot-common.README.Debian: simplified, we only point the user to
+ wiki.dovecot.org; added a note about how to regenerate the SSL certificate.
+ (Closes: #528934)
+ * debian/dovecot-common.postrm: do not break if /etc/ssl/certs does not
+ exist. (Closes: #524865)
+ * debian/patches/dovecot-managesieve-dist: updated to 0.10.6.
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 31 May 2009 20:13:26 +0200
+
+dovecot (1:1.1.13-2) unstable; urgency=high
+
+ * New upload, urgency set to high: the package in testing is broken.
+ * debian/dovecot-common.init: fixed a typo.
+ * debian/patches/dovecot-example.dpatch: fixed a few paths.
+ (Closes: #521544)
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 29 Mar 2009 21:16:02 +0200
+
+dovecot (1:1.1.13-1) unstable; urgency=low
+
+ * New upstream release.
+ * This version fixes problems accessing mailboxes in some setups.
+ (Closes: #520310)
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 19 Mar 2009 17:31:56 +0100
+
+dovecot (1:1.1.12-1) unstable; urgency=low
+
+ * New upstream release, it fixes some wrong defaults in the configuration
+ file. (Closes: #518631)
+ * debian/rules: add support for libdb. (Closes: #518630)
+ * debian/dovecot-common.postinst: create the dovecot certificate only at
+ install time. (Closes: #518738, #518598)
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 16 Mar 2009 14:26:32 +0100
+
+dovecot (1:1.1.11-4) unstable; urgency=low
+
+ * debian/dovecot-common.init: fixed a typo from the last upload.
+ (Closes: #518504)
+
+ -- Fabio Tranchitella <kobold@debian.org> Fri, 06 Mar 2009 18:39:09 +0100
+
+dovecot (1:1.1.11-3) unstable; urgency=low
+
+ * debian/dovecot-common.init: applied patch from HÃ¥kon Stordahl to call
+ ntp-wait if available. (Closes: #517808)
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 04 Mar 2009 17:38:59 +0100
+
+dovecot (1:1.1.11-2) unstable; urgency=low
+
+ * debian/dovecot-common.init: fixed a bug in the init script, adding a
+ missing slash in the PIDFILE path. (Closes: #516845)
+ * debian/dovecot-common.init: fixed a bug in the init script, sending the HUP
+ signal for reloading the configuration. (Closes: #512197)
+ * debian/dovecot-common.init: check if /etc/inetd.conf exists.
+ (Closes: #509259, #497148)
+ * debian/control: dovecot-common suggests ntp and ntpdate. (Closes: #511060)
+ * debian/dovecot-common.postinst: migration hook for default_mail_env =>
+ mail_location. (Closes: #517073)
+ * debian/dovecot.1: fixed the path of the configuration file.
+ (Closes: #501493)
+
+ -- Fabio Tranchitella <kobold@debian.org> Fri, 27 Feb 2009 17:24:09 +0100
+
+dovecot (1:1.1.11-1) unstable; urgency=low
+
+ * New upstream release, upload to unstable. (Closes: #507122)
+ * debian/patches/dovecot-quota.dpatch: fixed a bug in the imap quota plugin.
+ (Closes: #484677)
+ * debian/dovecot-common.init: added the status action, thanks Fladischer
+ Michael for the patch. (Closes: #509694)
+ * debian/dovecotpw.1: added manpage for dovecotpw, thanks Xavier Luthi for
+ the patch. (Closes: #504712)
+
+ -- Fabio Tranchitella <kobold@debian.org> Fri, 20 Feb 2009 20:39:38 +0100
+
+dovecot (1:1.1.9-1) experimental; urgency=low
+
+ [ Fabio Tranchitella ]
+ * debian/control: dovecot-common suggests ntp.
+
+ [ Joel Johnson ]
+ * New upstream release
+ * updated managesieve patch to apply against new version
+
+ -- Joel Johnson <mrjoel@lixil.net> Sat, 24 Jan 2009 04:12:42 +0000
+
+dovecot (1:1.1.8-1) experimental; urgency=low
+
+ * New upstream release.
+ * debian/control: added LDA to the description of dovecot-common.
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 07 Jan 2009 23:14:29 +0100
+
+dovecot (1:1.1.7-1) experimental; urgency=low
+
+ * New upstream release
+ * Updated dovecot-ssh.patch for new release
+ * Updated MANAGESIEVE to 0.10.4
+ * Fix package to support double compilation
+ - Properly clean dovecot-managesieve as pointed out by Stephan Bosch
+ - Add --copy directive to automake invocation
+
+ -- Joel Johnson <mrjoel@lixil.net> Thu, 04 Dec 2008 20:52:11 -0700
+
+dovecot (1:1.1.2-3) experimental; urgency=low
+
+ * debian/control: added libbz2-dev to the Build-Depends to enable the bzip2
+ support. (Closes: #495129)
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 14 Aug 2008 20:45:41 +0200
+
+dovecot (1:1.1.2-2) experimental; urgency=low
+
+ * Merged changes in the unstable package.
+ * debian/control: added replaces for the imap and pop modules from the
+ -common to the the -imap and -pop packages. (Closes: #493798)
+ * debian/rules: applied some of the suggestions from Matthias Kloses,
+ thanks! (Refs: #493803)
+
+ -- Fabio Tranchitella <kobold@debian.org> Tue, 05 Aug 2008 21:20:33 +0200
+
+dovecot (1:1.1.2-1) experimental; urgency=low
+
+ * New upstream release
+ * Trivial update to autocreate patch
+
+ -- Joel Johnson <mrjoel@lixil.net> Sat, 26 Jul 2008 08:18:39 -0600
+
+dovecot (1:1.1.1-1) experimental; urgency=low
+
+ [ Joel Johnson ]
+ * New upstream release (Closes: #487989)
+ * Updated policy version to 3.8.0.1 (no changes needed)
+ * Updated watch file to watch new release directory
+ * Actually install dovecot.8 since we have it
+ * Removed quota_v2 patch (was unreachable code): upstream now defaults
+ to quota v1 if _LINUX_QUOTA_VERSION is not defined
+ * Removed pam-error-information patch: included upstream
+ * Removed mbox_snarf patch: included upstream
+ * Removed full MANAGESIEVE patch (replaced with new 1.1 approach)
+ * Merged protocols_none_by_default patch into dovecot-example patch
+ * Updated drac patch to build against 1.1
+ * Updated autocreate patch with 1.1 version
+ * Updated dovecot-sieve to 1.1.5
+ * Added dovecot-managesieve patch against dovecot tree
+ * Added dovecot-managesieve source for building module
+
+ [ Fabio Tranchitella ]
+ * Added Joel Johnson to the uploaders; thanks for your work!
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 20 Jul 2008 19:09:23 +0200
+
+dovecot (1:1.0.15-2) unstable; urgency=low
+
+ * debian/dovecot.1: added "This includes doing a syntax check" to the -a
+ option.
+ * debian/rules: now the package builds two times in a row without problems.
+ (Closes: #490201)
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 27 Jul 2008 19:56:18 +0200
+
+dovecot (1:1.0.15-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/rules: clean the package before unpatching. (Closes: #490201)
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 22 Jun 2008 09:06:01 +0200
+
+dovecot (1:1.0.14-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/patches/inbox_creation.dpatch: removed, merged upstream.
+ * debian/patches/allow_nets.dpatch: removed, merged upstream.
+
+ -- Fabio Tranchitella <kobold@debian.org> Tue, 03 Jun 2008 10:07:13 +0200
+
+dovecot (1:1.0.13-6) unstable; urgency=low
+
+ * debian/patches/inbox_creation.dpatch: added, use mail_privileged_group's
+ group when creating inboxes if the unprivileged user fails; upstream:
+ http://hg.dovecot.org/dovecot-1.0/rev/932768a879c6 (Closes: #471716)
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 28 May 2008 12:58:17 +0200
+
+dovecot (1:1.0.13-5) unstable; urgency=low
+
+ * debian/patches/allow_nets.dpatch: added, allow_nets didn't work correctly
+ with big endian machines; patch from upstream, thanks Timo:
+ http://hg.dovecot.org/dovecot-1.0/rev/71c02fdf1b59
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 15 May 2008 14:48:14 +0200
+
+dovecot (1:1.0.13-4) unstable; urgency=low
+
+ * debian/patches/dovecot-MANAGESIEVE-9.3.dpatch: updated managesieve to
+ version 9.3.
+ * debian/dovecot-common.README.Debian: added a note about how to configure
+ dovecot to log to file instead of using syslog.
+ * debian/dovecot.1: added a SIGNALS section. (Closes: #479059)
+ * dovecot-sieve: updated to the last hg release (5c3ba11994cb).
+ (Closes: #479104)
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 05 May 2008 17:28:21 +0200
+
+dovecot (1:1.0.13-3) unstable; urgency=low
+
+ * debian/rules: do not install anymore the ldap and sql example
+ configuration files under /etc. (Closes: #472674)
+ * debian/dovecot-common.postinst: really chmod
+ /etc/dovecot/dovecot-{ldap,sql}.conf files to 0600.
+ * debian/devecot-common.init: do not start the service if dovecot.conf
+ doesn't exist. (Closes: #475888)
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 27 Apr 2008 22:42:37 +0200
+
+dovecot (1:1.0.13-2) unstable; urgency=low
+
+ * debian/rules: use aclocal-1.9 instead of aclocal. (Closes: #473754)
+
+ -- Fabio Tranchitella <kobold@debian.org> Tue, 01 Apr 2008 15:30:32 +0200
+
+dovecot (1:1.0.13-1) unstable; urgency=high
+
+ * New upstream release, fixes a security issue:
+ http://www.dovecot.org/list/dovecot-news/2008-March/000064.html
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 09 Mar 2008 19:22:20 +0100
+
+dovecot (1:1.0.12-1) unstable; urgency=high
+
+ * New upstream release. (Closes: #469457)
+ * debian/patches/dovecot-MANAGESIEVE-9.2.dpatch: updated, thanks to Marco
+ Nenciarini for the patch.
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 06 Mar 2008 15:53:07 +0100
+
+dovecot (1:1.0.10-4) unstable; urgency=low
+
+ * debian/patches/autocreate.dpatch: added, thanks to Walter Reiner.
+ * debian/rules: use --with-ioloop=best instead of --with-ioloop=epoll, as
+ suggested by Timo. (Closes: #466296)
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 18 Feb 2008 09:29:39 +0100
+
+dovecot (1:1.0.10-3) unstable; urgency=low
+
+ * debian/patches/dovecot-MANAGESIEVE-9.1.dpatch: added, thanks to Aleksey
+ Midenkov for providing a patch. (Closes: #416166)
+ * debian/dovecot-common.init: added $time to Should-Start. (Closes: #461543)
+ * debian/dovecot-common.postinst: do not add the dovecot user to the mail
+ group, it is not required by upstream. (Closes: #457123)
+ * debian/control: updated Standards-Version to 3.7.3, no changes required.
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 10 Feb 2008 18:37:55 +0100
+
+dovecot (1:1.0.10-2) unstable; urgency=low
+
+ * debian/patches/mbox_snarf.dpatch: added, thanks to Bernd Kuhls.
+ (Closes: #462319)
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 24 Jan 2008 10:12:02 +0100
+
+dovecot (1:1.0.10-1) unstable; urgency=high
+
+ * New upstream release, fixes a security bug.
+ * debian/patches/exec_check_for_none.dpatch: updated.
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 30 Dec 2007 10:29:26 +0100
+
+dovecot (1:1.0.9-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/control: added the Vcs-Svn and Vcs-Browser fields.
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 12 Dec 2007 08:10:11 +0100
+
+dovecot (1:1.0.8-2) unstable; urgency=low
+
+ * Provides a dovecot-dev package, thanks to Josh Triplett for providing a
+ patch. (Closes: #444812)
+
+ -- Fabio Tranchitella <kobold@debian.org> Tue, 04 Dec 2007 09:22:59 +0100
+
+dovecot (1:1.0.8-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/patches/unsupported-sasl-mech.dpatch: merged with upstream.
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 29 Nov 2007 13:36:39 +0100
+
+dovecot (1:1.0.7-3) unstable; urgency=low
+
+ * debian/patches/dovecot-ssl.dpatch: provide mechanism to discover if ssl
+ client certificate is verified, patch from Stephen Gran. (Closes: #446555)
+ * debian/patches/pam-error-information.dpatch: fill auth information in pam
+ error, patch backported from upstream RCS. (Closes: #439246)
+ * debian/patches/unsupported-sasl-mech.dpatch: should use NO (not BAD) for
+ unsupported SASL mech, patch backported from upstream RCS. (Closes: #449324)
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 14 Nov 2007 21:33:55 +0100
+
+dovecot (1:1.0.7-2) unstable; urgency=low
+
+ * debian/dovecot-common.postinst:
+ + don't fail if dovecot-ldap.conf or dovecot-sql.conf are removed; thanks
+ to Mathias Gug. (Closes: #448401)
+ + fix permissions of /var/run/dovecot and /var/run/dovecot/login.
+ (Closes: #446051)
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 04 Nov 2007 10:06:06 +0100
+
+dovecot (1:1.0.7-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/rules: remove drac.so in the clean target. (Closes: #442548)
+ * debian/dovecot-common.init: implemented the reload action.
+ (Closes: #441032)
+ * Update protocols option in configuration when installing/removing
+ -imapd/-pop3d packages. Thanks to Soren Hansen and Mathias Gug from
+ Ubuntu for providing a patch. (Closes: #447201)
+
+ -- Fabio Tranchitella <kobold@debian.org> Fri, 02 Nov 2007 23:06:17 +0100
+
+dovecot (1:1.0.5-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 10 Sep 2007 09:25:59 +0200
+
+dovecot (1:1.0.3-3) unstable; urgency=low
+
+ * debian/dovecot-common.init: don't use the init script name to locate the
+ configuration file because it is not reliable. If you really want to start
+ multiple servers, just copy the init script and use the optional default
+ file to override the variables. (Closes: #437228)
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 16 Aug 2007 09:17:01 +0200
+
+dovecot (1:1.0.3-2) unstable; urgency=low
+
+ * debian/rules: removed the --with-notify=inotify switch, it should be
+ detected automatically.
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 09 Aug 2007 09:39:50 +0200
+
+dovecot (1:1.0.3-1) unstable; urgency=low
+
+ * New upstream release. (Closes: #434038, #432601)
+ * This release doesn't build dbox support out-of-the-box. (Closes: #431615)
+ * dovecot-sieve: updated to the last hg's tip. (Closes: #434079)
+ * debian/dovecot-*.README.Debian: don't call /etc/init.d scripts directly.
+ (Closes: #431991)
+ * debian/dovecot-common.init: updated with patches from Tom Metro, thanks!
+ (Closes: #426480, #422674)
+ * debian/dovecot-common.postinst: fixed a missing variable DOMAINNAME.
+ (Closes: #425917)
+ * debian/rules: moved the init script to the level 24, after the ntpdate
+ one. (Closes: #432723)
+ * debian/patches/00list: added dovecot-drac, again. (Closes: #353039)
+ * debian/rules: build with inotify and epoll support. (Closes: #419281)
+ * debian/dovecot.1: added a simple manpage for dovecot. (Closes: #426702)
+ * debian/copyright: added copyright exceptions as suggested by Timo.
+
+ -- Fabio Tranchitella <kobold@debian.org> Sat, 04 Aug 2007 20:11:36 +0200
+
+dovecot (1:1.0.0-1) unstable; urgency=low
+
+ * New upstream release, the final 1.0.0. Bumped epoch, because we used the
+ wrong version scheming in the past and I think it is worth to do so now
+ that 1.0.0 has been released.
+ * debian/watch: updated.
+ * Rebuilt with a new glibc in unstable, now we have inotify support.
+ (Closes: #395306)
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 16 Apr 2007 08:42:32 +0200
+
+dovecot (1.0.rc31-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 09 Apr 2007 11:55:45 +0200
+
+dovecot (1.0.rc30-2) unstable; urgency=low
+
+ * debian/dovecot-common.init: check if /etc/inetd.conf exists before
+ calling sed on it. (Closes: #417299)
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 08 Apr 2007 16:58:30 +0200
+
+dovecot (1.0.rc30-1) unstable; urgency=low
+
+ * New upstream release.
+ *
+
+ -- Fabio Tranchitella <kobold@debian.org> Sat, 07 Apr 2007 11:17:45 +0200
+
+dovecot (1.0.rc29-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/rules: ship convert-tool in dovecot-common. (Closes: #416909)
+
+ -- Fabio Tranchitella <kobold@debian.org> Sat, 31 Mar 2007 14:15:39 +0200
+
+dovecot (1.0.rc28-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 25 Mar 2007 14:02:28 +0200
+
+dovecot (1.0.rc27-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 14 Mar 2007 14:39:04 +0100
+
+dovecot (1.0.rc26-4) unstable; urgency=low
+
+ * debian/dovecot-common.postinst: fixed a typo. (Closes: #414672)
+
+ -- Fabio Tranchitella <kobold@debian.org> Tue, 13 Mar 2007 08:45:47 +0100
+
+dovecot (1.0.rc26-3) unstable; urgency=low
+
+ * debian/control: depends on ucf (>= 2.0020). (Closes: #414032)
+ * debian/dovecot-common.postinst: handles better chmod/chown on package
+ upgrade. (Closes: #414257)
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 12 Mar 2007 10:03:34 +0100
+
+dovecot (1.0.rc26-2) unstable; urgency=low
+
+ * debian/control:
+ + dovecot-{imapd,pop3d}: depends on the same-source dovecot-common.
+ (Closes: #414032)
+ * debian/rules: fixed permission for dovecot.conf. (Closes: #413995)
+
+ -- Fabio Tranchitella <kobold@debian.org> Fri, 9 Mar 2007 12:57:50 +0100
+
+dovecot (1.0.rc26-1) unstable; urgency=low
+
+ * New upstream release.
+ * Add support for ucf, thanks to Vincent Danjean for providing a full patch.
+ (Closes: #413081)
+ * debian/dovecot-common.init: create /var/run directories at start-up time.
+ (Closes: #376143)
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 7 Mar 2007 11:26:56 +0100
+
+dovecot (1.0.rc25-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Fri, 2 Mar 2007 13:44:44 +0100
+
+dovecot (1.0.rc24-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Fri, 23 Feb 2007 15:47:30 +0100
+
+dovecot (1.0.rc23-1) unstable; urgency=low
+
+ * New upstream release.
+ * debian/patches/documentation.dpatch, debian/patches/xfs_quotas.dpatch:
+ removed, applied upstream.
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 21 Feb 2007 09:34:44 +0100
+
+dovecot (1.0.rc22-2) UNRELEASED; urgency=low
+
+ * debian/watch: added.
+
+ -- Fabio Tranchitella <kobold@debian.org> Fri, 9 Feb 2007 12:15:34 +0100
+
+dovecot (1.0.rc22-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 7 Feb 2007 09:38:34 +0100
+
+dovecot (1.0.rc21-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 5 Feb 2007 16:23:33 +0100
+
+dovecot (1.0.rc19-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 24 Jan 2007 00:03:38 +0100
+
+dovecot (1.0.rc18-2) unstable; urgency=low
+
+ * dovecot-sieve: updated to cvs version 1.0.1.
+ * debian/README.Debian: updated for the new setting mail_location, which
+ substitutes the old default_mail_env. (Closes: #408025)
+
+ -- Fabio Tranchitella <kobold@debian.org> Tue, 23 Jan 2007 10:15:36 +0100
+
+dovecot (1.0.rc18-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 22 Jan 2007 18:15:02 +0100
+
+dovecot (1.0.rc17-1) unstable; urgency=low
+
+ * New upstream release.
+ * Updated dovecot-sieve from CVS.
+
+ -- Fabio Tranchitella <kobold@debian.org> Fri, 12 Jan 2007 09:42:47 +0100
+
+dovecot (1.0.rc15-2) unstable; urgency=medium
+
+ * debian/dovecot-common.README.Debian: updated details about raw logging;
+ thanks Chris Moore for providing a patch. (Closes: #400689)
+ * debian/patches/dovecot-example.dpatch: added a missing slash for an
+ absolute path. (Closes: #400830)
+ * debian/patches/dovecotpw.dpatch: applied patched to fix argument parsing
+ on some architectures. (Closes: #402075)
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 18 Dec 2006 18:34:31 +0100
+
+dovecot (1.0.rc15-1) unstable; urgency=medium
+
+ * New upstream release.
+ * Fixes a security bug: Off-by-one buffer overflow with mmap_disable=yes.
+ (See: http://www.dovecot.org/list/dovecot-news/2006-November/000023.html)
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 20 Nov 2006 12:47:39 +0100
+
+dovecot (1.0.rc14-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 16 Nov 2006 09:37:38 +0100
+
+dovecot (1.0.rc13-1) unstable; urgency=medium
+
+ * New upstream release.
+ * debian/rules:
+ + preserve upstream config.guess and config.sub. (Closes: #397404)
+ + really clean dovecot-sieve/src on clean target. (Closes: #397407)
+ * debian/control: added build-conflict with automake1.4. (Closes: #397409)
+ * dovecot-sieve/src/Makefile.am: move sieve plug-ins under
+ usr/lib/dovecot/lda/modules; thanks to Chris Vanden Berghe for pointing
+ this out.
+
+ -- Fabio Tranchitella <kobold@debian.org> Tue, 7 Nov 2006 09:26:56 +0100
+
+dovecot (1.0.rc12-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 5 Nov 2006 16:52:52 +0100
+
+dovecot (1.0.rc10-3) unstable; urgency=medium
+
+ * debian/rules: fixed two typos in the configure call. (Closes: #395016)
+ * Included dovecot-sieve plug-in from CVS. (Closes: #394885)
+ * Urgency medium: we are near the freeze, and this release must be part
+ of etch.
+
+ -- Fabio Tranchitella <kobold@debian.org> Tue, 31 Oct 2006 06:30:45 +0000
+
+dovecot (1.0.rc10-2) unstable; urgency=low
+
+ * debian/patches/dovecot-example.dpatch: commented out a close brace.
+ (Closes: #394785)
+
+ -- Fabio Tranchitella <kobold@debian.org> Mon, 23 Oct 2006 08:17:13 +0000
+
+dovecot (1.0.rc10-1) unstable; urgency=low
+
+ * New upstream release. (Closes: #393004)
+ * debian/patches/dovecot-example.dpatch:
+ + added specific comments to the mail_extra_groups option.
+ (Closes: #383453)
+ + removed duplicated LDA section. (Closes: #391632)
+ * debian/dovecot.8: fixed a layout error. (Closes: #393080)
+ * debian/dovecot-common.init: added LSB headers.
+ * Switched to the upstream dovecot deliver (LDA).
+ * debian/patches/quota_v2.dpatch: added, thanks to Jonas Smedegaard.
+ (Closes: #377563)
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 22 Oct 2006 08:55:16 +0000
+
+dovecot (1.0.rc7-1) unstable; urgency=low
+
+ * New upstream release. (Closes: #377840, #385101)
+ * debian/patches/dovecot-example.dpatch: set a default value for
+ pop3_uidl_format. (Closes: #383883)
+
+ -- Fabio Tranchitella <kobold@debian.org> Tue, 29 Aug 2006 10:38:17 +0200
+
+dovecot (1.0.rc6-1) unstable; urgency=low
+
+ * New upstream release:
+ + Fixed imap segfaults on small mbox files (2 bytes). (Closes: #377840)
+ + Fixed a known bug in dovecot's IDLE handler. (Closes: #351828)
+ + Added support for quota2. (Closes: #377563)
+ * debian/control: converted build-depends on linux-kernel-headers to
+ build-conflicts to help the GNU/kFreeBSD port. (Closes: #377479)
+ * debian/control: changed maintainer to "Dovecot Maintainers"; no changes
+ to the email addresses.
+
+ -- Fabio Tranchitella <kobold@debian.org> Tue, 15 Aug 2006 10:58:57 +0200
+
+dovecot (1.0.rc2-2) unstable; urgency=low
+
+ * patched the quota plugin to fix a missing symbol (Closes: #377018)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 7 Jul 2006 10:50:04 -0400
+
+dovecot (1.0.rc2-1) unstable; urgency=high
+
+ * New upstream release
+ * Update dovecot-lda to the latest version (05132006)
+ * IPv6 with SSL/TLS should work now. (Closes: #374783)
+ * go back to using poll instead of epoll. (Closes: #376222)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 6 Jul 2006 00:47:56 -0400
+
+dovecot (1.0.rc1-1) unstable; urgency=low
+
+ * New upstream release.
+ * Add a build-dependency on linux-kernel-headers for the xfs quotas
+ stuff. Make it higher than the version in sarge because sarges xfs
+ includes are too old. If any knowledgeable person would like to
+ give me a patch for this, please do. (Closes: #374793)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 28 Jun 2006 11:42:07 -0400
+
+dovecot (1.0.beta9-1) unstable; urgency=low
+
+ * New upstream release
+ * Added XFS quota support. Thanks Pawel Jarosz <pj@rsi.pl>
+ (Closes: #373936)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 19 Jun 2006 16:55:20 -0400
+
+dovecot (1.0.beta8-4) unstable; urgency=high
+
+ * Unfortunately, the patch in the last version broke the mysql module.
+ Fixed thanks to Martin Pitt. (Closes: #369359, #373227)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 11 Jun 2006 16:27:43 -0400
+
+dovecot (1.0.beta8-3) unstable; urgency=high
+
+ * [SECURITY] SQL injection could occur in the postgresql module with
+ certain client character encodings. (See CVE-2006-2314)
+ Used the patch from upstream and Martin Pitt <martin.pitt@ubuntu.com>.
+ Thanks Martin. (Closes: #369359)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 11 Jun 2006 15:33:55 -0400
+
+dovecot (1.0.beta8-2) unstable; urgency=high
+
+ * Don't chown/chmod ssl certificate unless we created it.
+ (Closes: #364766)
+ * Upstream fixed the crash if passwd-file had entries without passwords.
+ (Closes: #361536)
+ * fixed up the last versions changelog to better describe the security
+ problem which was fixed there.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 21 May 2006 13:16:17 -0400
+
+dovecot (1.0.beta8-1) unstable; urgency=high
+
+ * New upstream release.
+ * [SECURITY] Fixes a directory traversal vulnerability.
+ (see: http://www.dovecot.org/list/dovecot-news/2006-May/000006.html
+ and CVE-2006-2414)
+ * Set urgency to high: this version fixes a security bug
+ * Standards-Version: 3.7.2, no changes needed.
+
+ -- Fabio Tranchitella <kobold@debian.org> Sat, 13 May 2006 22:46:16 +0200
+
+dovecot (1.0.beta7-1) unstable; urgency=low
+
+ * New upstream version.
+ * Added sqlite support.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 12 Apr 2006 23:25:41 -0400
+
+dovecot (1.0.beta5-1) unstable; urgency=low
+
+ * New upstream version. Also updated dovecot-lda from CVS.
+ * debian/control. Added build-depends on flex to prevent FTBFS.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 6 Apr 2006 16:22:46 -0400
+
+dovecot (1.0.beta3-3) unstable; urgency=low
+
+ * Compile against the newer mysql library. (Closes: #356729)
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 22 Mar 2006 13:43:04 +0000
+
+dovecot (1.0.beta3-2) unstable; urgency=low
+
+ [Fabio Tranchitella]
+ * debian/control: added build-depends on byacc.
+ * debian/rules: removed --with-vpopmail option, because libvpopmail-dev
+ is in contrib and we don't wanto to have dovecot build-depends on it.
+ * debian/patches/dovecot-example.dpatch: added two small commented
+ block of configuration for dovecot-lda.
+
+ -- Fabio Tranchitella <kobold@debian.org> Sun, 26 Feb 2006 20:59:06 +0000
+
+dovecot (1.0.beta3-1) unstable; urgency=high
+
+ [Fabio Tranchitella]
+ * New upstream release, which fixes two security related bugs.
+ CVE-2006-0730 (Closes: #353341)
+ * Included dovecot-lda (ver. 20060209). (Closes: #353307, #347348, #333962)
+
+ [ Jaldhar H. Vyas ]
+ * Removed the code for upgrading impad.pem. This might bite if you if
+ you try and upgrade a woody version of dovecot to this one. So
+ don't do that. (Closes: #337715)
+ * dovecot-imapd,dovecot-pop3d: depend on dovecot-common >= 1.0beta3-1
+ as the way SSL parameters are generated has changed. (Closes: #353404)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 17 Feb 2006 22:06:21 -0500
+
+dovecot (1.0.beta2-1) unstable; urgency=low
+
+ [Fabio Tranchitella]
+ * New upstream release.
+ * debian/rules: compile with vpopmail support. (Closes: #347838)
+ * debian/patches: removed zlib patch (merged with upstream).
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 2 Feb 2006 21:38:32 +0000
+
+dovecot (1.0.alpha5-1) unstable; urgency=low
+
+ [Fabio Tranchitella]
+ * New upstream release.
+ * Compile dovecot with Kerberos support. (Closes: #338384)
+ * Fixed a small typo in mbox specification. (Closes: #339789)
+ * Added man page for maildirmake.dovecot, thanks to Henry Precheur.
+ (Closes: #340498)
+ * Use /usr/lib/dovecot/modules as basedir for dynamic modules.
+ Upstream suggests /usr/lib/dovecot, but we already use it as
+ libexec directory.
+
+ -- Fabio Tranchitella <kobold@debian.org> Wed, 21 Dec 2005 13:44:38 +0000
+
+dovecot (1.0.alpha4-1) unstable; urgency=low
+
+ [Jaldhar H. Vyas]
+ * New upstream version.
+ * Made sure the default dovecot.conf includes mail_extra_groups=mail
+ (Closes: #336476) somehow this edit got lost at some point.
+ * use ISO8601 date format as default value for log_timestamp in
+ /etc/dovecot/dovecot.conf (Closes: #333059)
+ * stop shipping {arch} directories in source (Closes: #334646)
+ * Include plugin for compressed mboxen (Closes: #332384)
+ * updated NEWS.Debian to warn users that the dovecot.conf syntax has
+ changed (Closes: #334209)
+ * Remember, remember, the 5th of November.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 5 Nov 2005 23:19:19 -0500
+
+dovecot (1.0.alpha3-2) unstable; urgency=low
+
+ [Jaldhar H. Vyas]
+ * dovecot-common: When creating the dovecot user in the postinst,
+ the --ingroup option to adduser to add dovecot to group
+ mail isn't used anymore.
+ (Closes: #330960, #331106)
+ * commented out userdb passdb from default configuration. Most
+ people won't need that. (Closes: #330978)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 6 Oct 2005 14:25:33 -0400
+
+dovecot (1.0.alpha3-1) unstable; urgency=low
+
+ [ Fabio Tranchitella ]
+ * New upstream release (dovecot-1.0.alpha.3)
+ * debian/patches/ipv6_v6only.dpatch: removed, upstream accepted it.
+ * debian/dovecot-common.postinst: removed bashisms.
+ * debian/dovecot-common.postinst: add dovecot user to group mail.
+ (Closes: #323921)
+ * debian/control: removed conflicts with imap-server and pop3-server,
+ added replaces instead. (Closes: #324480)
+ [ Jaldhar H. Vyas ]
+ * No longer crashes when using LDAP as userdb/passdb (Closes: #320388)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 26 Sep 2005 01:42:09 -0400
+
+dovecot (0.99.20050712-2) unstable; urgency=low
+
+ * Fabio Tranchitella <kobold@debian.org>
+ + debian/control: dovecot-common has to depend on adduser.
+ + debian/patches/documentation.dpatch: some cosmetic fixes about mysql
+ backend.
+ * Jaldhar H. Vyas <jaldhar@debian.org>
+ + debian/control: tighten dovecot-imapd and dovecot-pop3d's dependency on
+ dovecot-common (Closes: #319465)
+ + debian/patches/dovecot-example.dpatch: some more fixes to default
+ configuration. (Closes: #319413, #319941)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 29 Jul 2005 15:37:52 -0400
+
+dovecot (0.99.20050712-1) unstable; urgency=low
+
+ * Fabio Tranchitella <kobold@debian.org>
+ + New upstream version (dovecot-stable, last update 20050712).
+ (Closes: #312893)
+ + debian/control: Standards-Version: 3.6.2 (no changes needed).
+ + debian/patches/dovecot-sql.dpatch: use the right path for mysql socket.
+ (Closes: #298874)
+ * Jaldhar H. Vyas <jaldhar@debian.org>
+ + Removed dovecot package as it was just a woody->sarge transitional
+ pseudo-package.
+ + Apply patch to debian/dovecot-common.init to help when manually
+ starting dovecot. Thanks Roland Stigge. (Closes: #309679)
+ + Apply patch to src/lib/network.c to support IPV6_V6ONLY. Thanks
+ Marco D'Itri. (Closes: #308652)
+ + depend on the latest postgresql library.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 20 Jul 2005 06:30:37 -0400
+
+dovecot (0.99.14-1) unstable; urgency=low
+
+ * New upstream version.
+ * dovecot-common: another postinst regexp fix for SSL cert/key files.
+ (Closes: #294989)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 12 Feb 2005 21:34:33 -0500
+
+dovecot (0.99.13-6) unstable; urgency=high
+
+ * dovecot-common: *sigh* another init script fix. Hopefully we now
+ fully deal with dovecot being run from inetd. Thanks again to Magnus
+ Holmgren. (Closes: #293348)
+ * High again so -5 doesn't get into sarge.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 7 Feb 2005 02:58:30 -0500
+
+dovecot (0.99.13-5) unstable; urgency=high
+
+ * dovecot-common: typo in postinst resulted in incorrect generation of
+ keys for first-time installers. Hence urgency high.
+ * dovecot-common: In init script, make extra check to make sure an
+ IMAP or POP3 server called from inetd is dovecot and not some other
+ random inferior product. (Closes: #293348)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 5 Feb 2005 13:56:31 -0500
+
+dovecot (0.99.13-4) unstable; urgency=low
+
+ * build depend on libmysqlclient12
+ * dovecot-common: Allow STARTTLS to work when dovecot is run from inetd
+ Thanks Magnus Holmgren (Closes: #290985)
+ * dovecot-common: let init script exit if dovecot is being run from inetd
+ Thanks Magnus Holmgren (Closes: #292195)
+ * dovecot-common: fix a number of problems in postinst
+ + fails if /etc/ssl/certs or /etc/ssl/private doesn't exist
+ + certs cannot be generated and upgrade fails if openssl is not
+ configured. Fail more gracefully if this is the case.
+ + read the name and path for the cert from dovecot.conf instead of
+ hardcoding it.
+ Thanks Frederic Pauget (Closes: #292344)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 30 Jan 2005 15:20:03 -0500
+
+dovecot (0.99.13-3) unstable; urgency=high
+
+ * Oops -2 had to be urgency=high so -1 doesn't get into sarge.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 8 Jan 2005 12:11:38 -0500
+
+dovecot (0.99.13-2) unstable; urgency=low
+
+ * dovecot-imapd, dovecot-pop3d: It occurred to me that the effects of
+ fixing #288391 will cause confusion in the minds of new installers so I
+ should add a warning in README.Debian and NEWS.Debian in a vain
+ effort to stave off swarms of bug reports. (Vain, because no one
+ actually reads documentation anyway.)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 8 Jan 2005 11:29:59 -0500
+
+dovecot (0.99.13-1) unstable; urgency=high
+
+ * New upstream version.
+ * dovecot-imapd, dovecot-pop3d: No longer mess with dovecot.conf in postinst
+ (Closes: #288391)
+ * urgency high due to #288391 being a release-critical bug.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 7 Jan 2005 17:37:08 -0500
+
+dovecot (0.99.12-1) unstable; urgency=low
+
+ * New upstream version. (Yes I know 0.99.13 is just around the corner.)
+ * SASL is re-enabled so this bug ("Dovecot seems not to require SASL")
+ is no longer valid (Closes: #272093)
+ * Configuration files moved to /etc/dovecot (Closes: #276183)
+ * Permissions on /var/run/dovecot and /var/run/dovecot/login no longer
+ give warnings. (Closes: #283996)
+ * SSL certificate is world readable (Closes: #277114).
+ * Thanks to Jan Buren, much extra documentation has been added to
+ /usr/share/doc/dovecot-common/README.Debian
+ * Lintian overrides added.
+ * Happy new year to all you Gregorians
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 31 Dec 2004 15:55:07 -0500
+
+dovecot (0.99.11-3) unstable; urgency=medium
+
+ * applied dovecot-large-header-fix patch to prevent 100% CPU
+ utilization when dealing with really large headers.
+ (Closes: #271458)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 13 Sep 2004 20:20:23 -0400
+
+dovecot (0.99.11-2) unstable; urgency=low
+
+ * Eliminated duplicate stanza in dovecot.conf (Closes: #270181)
+ * Reapplied CRAM-MD5 patch.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 6 Sep 2004 01:24:53 -0400
+
+dovecot (0.99.11-1) unstable; urgency=low
+
+ * New upstream release.
+ * patch to give bug reporting address in configure.ac.
+ Thanks Matthias Andree.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 4 Sep 2004 14:24:57 -0400
+
+dovecot (0.99.10.9-2) unstable; urgency=low
+
+ * screw mipsel.
+ * Added PAM_RHOST patch. Thanks Dean Gaudet. (Closes: #264712)
+ * Added CRAM-MD5 patch. Thanks Joshua Goodall.
+ * Added unexpected EOF patch from Timo.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Tue, 17 Aug 2004 01:13:20 -0400
+
+dovecot (0.99.10.9-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 2 Aug 2004 18:56:02 -0400
+
+dovecot (0.99.10.8-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 30 Jul 2004 08:17:51 -0400
+
+dovecot (0.99.10.7-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 14 Jul 2004 07:29:49 -0400
+
+dovecot (0.99.10.6-3) unstable; urgency=low
+
+ * Patched so dovecot follows symlinks to directories again.
+ (Closes: #256061)
+ * Changed the priority of init script so it is run after postgresql
+ (Closes: #256068)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 24 Jun 2004 23:57:50 -0400
+
+dovecot (0.99.10.6-2) unstable; urgency=high
+
+ * I needed to enable one more parameter in the configuration in order
+ to get dot-locking working. Hence this should still be high urgency.
+ (Really Closes: #185335)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 21 Jun 2004 20:02:29 -0400
+
+dovecot (0.99.10.6-1) unstable; urgency=high
+
+ * New upstream version.
+ + finally fixes dot-locking so I think it deserves high priority for
+ sarge. (Closes: #185335)
+ * dovecot: fixed a typo in description (Closes: #254415)
+ * dovecot-common: man page for dovecot added. Thanks Kai Hendry.
+ (Closes: #253482)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 19 Jun 2004 23:18:39 -0400
+
+dovecot (0.99.10.5-4) unstable; urgency=high
+
+ * Crap, typo in dovecot-common.postinst sorry. This should only
+ affect new installs though.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 11 Jun 2004 22:30:41 -0400
+
+dovecot (0.99.10.5-3) unstable; urgency=high
+
+ * SECURITY: set permissions on config files to 0600 to prevent
+ disclosure of sensitive information to local users. (Closes: #253760)
+ * SECURITY: Tightened permissions on generated SSL certificate.
+ (Closes: #253833)
+ * Made a note that dovecot-openssl.conf is not needed on Debian
+ because we generate a certificate in dovecot-commons' postinst.
+ (Closes: #253774)
+ * Added maildir-autocreate patch.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 11 Jun 2004 09:12:07 -0400
+
+dovecot (0.99.10.5-2) unstable; urgency=low
+
+ * Added maildir-stat patch.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 10 Jun 2004 14:39:28 -0400
+
+dovecot (0.99.10.5-1) unstable; urgency=low
+
+ * New upstream version.
+ * Enabled mysql support.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 27 May 2004 13:21:42 -0400
+
+dovecot (0.99.10.4-5) unstable; urgency=high
+
+ * Switched to using openssl as dovecot segfaults with gnutls7 and is
+ not compatible with gnutls10 (Closes: #244570)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 19 Apr 2004 22:45:21 -0400
+
+dovecot (0.99.10.4-4) unstable; urgency=low
+
+ * Added a patch to src/auth/db-pgsql.c from Zsolt VARGA.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Tue, 30 Mar 2004 12:49:31 -0500
+
+dovecot (0.99.10.4-3) unstable; urgency=high
+
+ * dovecot-common: Fix postinst to no longer delete /etc/pam.d/imap
+ (Closes: #232832)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 15 Mar 2004 10:27:52 -0500
+
+dovecot (0.99.10.4-2) unstable; urgency=low
+
+ * dovecot-common: now replaces: dovecot for smoother upgrades.
+ (Closes: #223666)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 11 Dec 2003 09:18:12 -0500
+
+dovecot (0.99.10.4-1) unstable; urgency=low
+
+ * New upstream version
+ + This fixes the corruption of .subscriptions files with folders in
+ maildir format. (Closes: #222272)
+ * Some extra information included in dovecot-common.README.Debian.
+ (Closes: #221106) There should probably be more so if you have ideas
+ let me know.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 1 Dec 2003 23:41:00 -0500
+
+dovecot (0.99.10.2-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Tue, 11 Nov 2003 17:21:45 -0500
+
+dovecot (0.99.10-11) unstable; urgency=low
+
+ * Build depend on gnutls 7 instead of 5 (Closes: #219523)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 6 Nov 2003 22:25:00 -0500
+
+dovecot (0.99.10-10) unstable; urgency=low
+
+ * maildirmake.dovecot will now let you create maildirs whose names
+ have spaces in them and chown them to a specified user. Thanks Paul
+ Slootman (Closes: #219168)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Tue, 4 Nov 2003 20:00:25 +0000
+
+dovecot (0.99.10-9) unstable; urgency=low
+
+ * Don't use SASL2 as upstream says the support is broken.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Tue, 23 Sep 2003 16:27:11 +0000
+
+dovecot (0.99.10-8) unstable; urgency=low
+
+ * Patched so suid works on 2.6 kernels. Thanks Peter Gervai.
+ (Closes: #211420)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 17 Sep 2003 17:59:10 +0000
+
+dovecot (0.99.10-7) unstable; urgency=low
+
+ * Yet another init script fix. It should be ok now. Thanks once again
+ to Alexis Iglauer.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 27 Aug 2003 10:56:04 -0400
+
+dovecot (0.99.10-6) unstable; urgency=low
+
+ * fix some errors in init script (closes: #207464)
+ Thanks to Adam Lackorzynski and Alexis Iglauer.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 27 Aug 2003 09:02:23 -0400
+
+dovecot (0.99.10-5) unstable; urgency=high
+
+ * dovecot-pop3d, dovecot-imapd: make sure init script doesn't
+ attempt to start the daemons if unconfigured thus preventing
+ segfault on startup. (Closes: #206992, #207140)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 25 Aug 2003 16:40:53 -0400
+
+dovecot (0.99.10-4) unstable; urgency=low
+
+ * Updated PAM configuration to the new scheme and added appropriate
+ dependency.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 22 Aug 2003 12:54:54 -0400
+
+dovecot (0.99.10-3) unstable; urgency=low
+
+ * dovecot-pop3d: Patch for proper PAM service name.
+ * dovecot-imapd, dovecot-pop3d: Make sure there is an appropriate
+ entry in the protocol = line in /etc/dovecot.conf so the service
+ will start up without errors. (Closes: #204213)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 21 Aug 2003 13:47:00 -0400
+
+dovecot (0.99.10-2.1) unstable; urgency=low
+
+ * Non-maintainer upload at request of maintainer.
+ * Fix segfault on alpha caused by time_t size. Closes: #203892.
+ * Fix segfault when user's home directory is left empty.
+
+ -- Scott James Remnant <scott@netsplit.com> Wed, 6 Aug 2003 01:47:16 +0100
+
+dovecot (0.99.10-2) unstable; urgency=low
+
+ * corrected paths to example and configuration files in sample config
+ (Closes: #199740)
+ * Added postgresql support.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 3 Jul 2003 16:45:49 -0400
+
+dovecot (0.99.10-1) unstable; urgency=low
+
+ * New upstream release.
+ * PAM service name has changed to dovecot (for IMAP and POP3.) I've included
+ code to move /etc/pam.d/imap to /etc/pam.d/dovecot but if things suddenly
+ stop working, this is the first thing to check.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Thu, 26 Jun 2003 22:31:07 -0400
+
+dovecot (0.99.10-0.rc2) unstable; urgency=low
+
+ * New upstream release. Fixes broken imaps support.
+ * Typo in configure options that broke LDAP support on woody corrected.
+ * Only start /usr/sbin/dovecot if either the IMAP or POP3 servers are
+ installed. (Closes: #192066)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 23 Jun 2003 23:26:04 -0400
+
+dovecot (0.99.9.1-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 4 May 2003 21:49:55 -0400
+
+dovecot (0.99.9-1) unstable; urgency=low
+
+ * New upstream release.
+ * The IMAP and POP3 servers have been split into separate package so you
+ don't have to install both. There is also a dovecot-common package
+ for the parts they share. The dovecot package is now a dummy just for
+ transitioning to this new scheme. (Closes: #187826)
+ * Allow chmod in maildirmake.dovecot to fail gracefully (Closes: #191244)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 30 Apr 2003 08:53:46 -0400
+
+dovecot (0.99.8.1-4) unstable; urgency=low
+
+ * Added a build-depends on libsasl-dev (Closes: #187516)
+ * Enabled pop3 service. However it is still turned off in the config file by
+ default so as to not surprise anyone who thought they only had an IMAP
+ server.
+ * Consequently, changed "IMAP server" in descriptions etc. to "mail server".
+ * only skip key generation if both /etc/ssl/certs/dovecot.pem and
+ /etc/ssl/private/dovecot.pem exist. (Closes: #187638)
+ * post-0.99.8.1 patch: Fix renaming subfolders with maildir.
+ * post-0.99.8.1 patch: Fix other maildir subfolder problems.
+ * post-0.99.8.1 patch: Fix partial body fetches.
+ * post-0.99.8.1 patch: Fix using LITERAL+APPEND.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sat, 5 Apr 2003 14:13:52 -0500
+
+dovecot (0.99.8.1-3) unstable; urgency=low
+
+ * Fixed bashism and perlism(!) in maildirmake.dovecot. Thanks Clint
+ Adams. (Closes: #185768)
+ * Enabled LDAP support. The configuration is in /etc/dovecot-ldap.conf
+ but is commented out.
+ * Happy new year to all Debian users still on the Julian calendar.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Tue, 25 Mar 2003 09:25:37 -0500
+
+dovecot (0.99.8.1-2) unstable; urgency=low
+
+ * Make a separate check for /etc/ssl/private/imapd.pem in case we don't
+ have it. (Closes: #185334)
+ * Move check for dovecot user and creation code to postinsts' configure
+ phase. (Closes: #185333)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 19 Mar 2003 01:50:10 -0500
+
+dovecot (0.99.8.1-1) unstable; urgency=low
+
+ * new upstream release. (Closes: #184131) Does not include the POP3
+ server or LDAP and SASL support for now so we can get into the archive
+ quickly. Fixes segfaults in imap-master (Closes: #184231) Fixes
+ faulty mail autodetection presets (Closes: #179625)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Sun, 16 Mar 2003 15:47:54 -0500
+
+dovecot (0.99.7-4) unstable; urgency=low
+
+ * Fixed location of handlers. (Closes: #179273)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 31 Jan 2003 18:37:23 -0500
+
+dovecot (0.99.7-3) unstable; urgency=low
+
+ * doh pam file should be called imap not dovecot (Closes: #179180)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 31 Jan 2003 14:12:29 -0500
+
+dovecot (0.99.7-2) unstable; urgency=low
+
+ * Added two upstream patches to fix broken plain authentication and
+ building with vpopmail support (which is not enabled in the Debian
+ package yet.)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 17 Jan 2003 14:24:22 -0500
+
+dovecot (0.99.7-1) unstable; urgency=low
+
+ * New upstream release.
+ * Syslog no longer fills up with entries when dovecot is misconfigured.
+ (Closes: #175507)
+ * startup is logged now. shutdowns were already logged. (Closes: #175509)
+ * dovecot will not remove your SSL certificates now. (Closes: #175282)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 15 Jan 2003 00:58:23 -0500
+
+dovecot (0.99.6rc2-1) unstable; urgency=low
+
+ * New upstream release.
+ * Includes patch which stops dovecot from dying on alpha (Closes: #175577)
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 8 Jan 2003 14:22:09 -0500
+
+dovecot (0.99.5-1) unstable; urgency=low
+
+ * New upstream release with many fixes and improvements.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Fri, 3 Jan 2003 00:53:26 -0500
+
+dovecot (0.99.4-1) unstable; urgency=low
+
+ * Initial Release.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 18 Dec 2002 09:44:39 -0500
--- /dev/null
+##
+## Authentication processes
+##
+
+# Enable LOGIN command and all other plaintext authentications even if
+# SSL/TLS is not used (LOGINDISABLED capability). Note that if the remote IP
+# matches the local IP (ie. you're connecting from the same computer), the
+# connection is considered secure and plaintext authentication is allowed,
+# unless ssl = required.
+#auth_allow_cleartext = yes
+
+# Authentication cache size (e.g. 10M). 0 means it's disabled. Note that
+# bsdauth, PAM and vpopmail require cache_key to be set for caching to be used.
+#auth_cache_size = 0
+# Time to live for cached data. After TTL expires the cached record is no
+# longer used, *except* if the main database lookup returns internal failure.
+# We also try to handle password changes automatically: If user's previous
+# authentication was successful, but this one wasn't, the cache isn't used.
+# For now this works only with plaintext authentication.
+#auth_cache_ttl = 1 hour
+# TTL for negative hits (user not found, password mismatch).
+# 0 disables caching them completely.
+#auth_cache_negative_ttl = 1 hour
+
+# Space separated list of realms for SASL authentication mechanisms that need
+# them. You can leave it empty if you don't want to support multiple realms.
+# Many clients simply use the first one listed here, so keep the default realm
+# first.
+#auth_realms =
+#
+# Default realm/domain to use if none was specified. This is used for both
+# SASL realms and appending @domain to username in plaintext logins.
+#auth_default_domain =
+
+# List of allowed characters in username. If the user-given username contains
+# a character not listed in here, the login automatically fails. This is just
+# an extra check to make sure user can't exploit any potential quote escaping
+# vulnerabilities with SQL/LDAP databases. If you want to allow all characters,
+# set this value to empty.
+#auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
+
+# Username character translations before it's looked up from databases. The
+# value contains series of from -> to characters. For example "#@/@" means
+# that '#' and '/' characters are translated to '@'.
+#auth_username_translation =
+
+# Username formatting before it's looked up from databases.
+#auth_username_format = %{user|lower}
+#auth_username_format = %{user|username|lower}
+
+# If you want to allow master users to log in by specifying the master
+# username within the normal username string (ie. not using SASL mechanism's
+# support for it), you can specify the separator character here. The format
+# is then <username><separator><master username>. UW-IMAP uses "*" as the
+# separator, so that could be a good choice.
+#auth_master_user_separator =
+
+# Username to use for users logging in with ANONYMOUS SASL mechanism
+#auth_anonymous_username = anonymous
+
+# Host name to use in GSSAPI principal names. The default is to use the
+# name returned by gethostname(). Use "$ALL" (with quotes) to allow all keytab
+# entries.
+#auth_gssapi_hostname =
+
+# Kerberos keytab to use for the GSSAPI mechanism. Will use the system
+# default (usually /etc/krb5.keytab) if not specified. You may need to change
+# the auth service to run as root to be able to read this file.
+#auth_krb5_keytab =
+
+# Do NTLM and GSS-SPNEGO authentication using Samba's winbind daemon and
+# ntlm_auth helper. <https://doc.dovecot.org/latest/core/config/auth/mechanisms/winbind.html>
+#auth_use_winbind = no
+
+# Path for Samba's ntlm_auth helper binary.
+#auth_winbind_helper_path = /usr/bin/ntlm_auth
+
+# Time to delay before replying to failed authentications.
+#auth_failure_delay = 2 secs
+
+# Require a valid SSL client certificate or the authentication fails.
+#auth_ssl_require_client_cert = no
+
+# Take the username from client's SSL certificate, using
+# X509_NAME_get_text_by_NID() which returns the subject's DN's
+# CommonName.
+#auth_ssl_username_from_cert = no
+
+# Space separated list of wanted authentication mechanisms:
+# plain login digest-md5 cram-md5 ntlm anonymous gssapi
+# gss-spnego xoauth2 oauthbearer
+# NOTE: See also auth_allow_cleartext setting.
+#auth_mechanisms = plain login
+
+##
+## Password and user databases
+##
+
+#
+# Password database is used to verify user's password (and nothing more).
+# You can have multiple passdbs and userdbs. This is useful if you want to
+# allow both system users (/etc/passwd) and virtual users to login without
+# duplicating the system users into virtual database.
+#
+# <https://doc.dovecot.org/latest/core/config/auth/passdb.html>
+#
+# User database specifies where mails are located and what user/group IDs
+# own them. For single-UID configuration use "static" userdb.
+#
+# <https://doc.dovecot.org/latest/core/config/auth/userdb.html>
+
+#!include auth-deny.conf.ext
+#!include auth-master.conf.ext
+#!include auth-oauth2.conf.ext
+
+!include auth-system.conf.ext
+#!include auth-sql.conf.ext
+#!include auth-ldap.conf.ext
+#!include auth-passwdfile.conf.ext
+#!include auth-static.conf.ext
--- /dev/null
+##
+## Log destination.
+##
+
+# Log file to use for error messages. "syslog" logs to syslog,
+# /dev/stderr logs to stderr.
+#log_path = syslog
+
+# Log file to use for informational messages. Defaults to log_path.
+#info_log_path =
+# Log file to use for debug messages. Defaults to info_log_path.
+#debug_log_path =
+
+# Syslog facility to use if you're logging to syslog. Usually if you don't
+# want to use "mail", you'll use local0..local7. Also other standard
+# facilities are supported.
+#syslog_facility = mail
+
+##
+## Logging verbosity and debugging.
+##
+
+# Log unsuccessful authentication attempts and the reasons why they failed.
+#auth_verbose = yes
+
+# In case of password mismatches, log the attempted password. Valid values are
+# no, plain and sha1. sha1 can be useful for detecting brute force password
+# attempts vs. user simply trying the same password over and over again.
+#auth_verbose_passwords = no
+
+# To chain multiple logging conditions you can use,
+# log_debug=$SET:log_debug or category=xxx
+
+# Even more verbose logging for debugging purposes. Shows for example SQL
+# queries.
+#log_debug=category=auth
+#
+# In case of password mismatches, log the passwords and used scheme so the
+# problem can be debugged. Enabling this also enables auth_debug.
+#auth_debug_passwords = yes
+
+# Enable mail process debugging. This can help you figure out why Dovecot
+# isn't finding your mails.
+#log_debug=category=mail
+
+# Show protocol level SSL errors.
+#log_debug=category=ssl
+
+# mail_log plugin provides more event logging for mail processes.
+#mail_plugins {
+# notify = yes
+# mail_log = yes
+#}
+# Events to log. Also available: flag_change append
+#mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename flag_change append
+# Available fields: uid, box, msgid, from, subject, size, vsize, flags
+# size and vsize are available only for expunge and copy events.
+#mail_log_fields = uid box msgid size from subject vsize flags
+# only log cached fields
+#mail_log_cached_only = yes
+
+##
+## Log formatting.
+##
+
+# Prefix for each line written to log file. % codes are in strftime(3)
+# format.
+#log_timestamp = "%b %d %H:%M:%S "
+
+# Space-separated list of elements we want to log. The elements which have
+# a non-empty variable value are joined together to form a comma-separated
+# string.
+#login_log_format_elements = user=<%{user}> method=%{mechanism} rip=%{remote_ip} lip=%{local_ip} mpid=%{mail_pid} %{secured} session=<%{session}>
+
+# Login log format. %{elements} contains login_log_format_elements string, %{message} contains
+# the data we want to log.
+#login_log_format = %{message}: %{elements}
+
+# Log prefix for mail processes. See
+# https://doc.dovecot.org/latest/core/settings/variables.html#mail-service-user-variables
+# for list of possible variables.
+#mail_log_prefix = "%{service}(%{user})<%{process:pid}><%{session}>: "
+
+# Format to use for logging mail deliveries. See https://doc.dovecot.org/latest/core/summaries/settings.html#deliver_log_format
+# for list of possible variables.
+#deliver_log_format = msgid=%{msgid}: %{message} (subject=%{subject} from=%{from} size=%{size})
--- /dev/null
+##
+## Mailbox locations and namespaces
+##
+
+# Location for users' mailboxes. The default is empty, which means that Dovecot
+# tries to find the mailboxes automatically. This won't work if the user
+# doesn't yet have any mail, so you should explicitly tell Dovecot the full
+# location.
+#
+# If you're using mbox, giving a path to the INBOX file (eg. /var/mail/%u)
+# isn't enough. You'll also need to tell Dovecot where the other mailboxes are
+# kept. This is called the "root mail directory", and it must be the first
+# path given in the mail_location setting.
+#
+# There are a few special variables you can use, eg.:
+#
+# %{user} - username
+# %{user|username} - user part in user@domain, same as %u if there's no domain
+# %{user|domain} - domain part in user@domain, empty if there's no domain
+# %{home} - home directory
+#
+# See https://doc.dovecot.org/latest/core/settings/variables.html for full list
+# of variables.
+#
+# Example:
+# mail_driver = maildir
+# mail_path = ~/Maildir
+# mail_inbox_path = ~/Maildir/.INBOX
+#
+
+# Debian defaults
+# Note that upstream considers mbox deprecated and strongly recommends
+# against its use in production environments. See further information
+# at
+# https://doc.dovecot.org/2.4.1/core/config/mailbox/formats/mbox.html
+mail_driver = mbox
+mail_home = /home/%{user | username}
+mail_path = %{home}/mail
+mail_inbox_path = /var/mail/%{user}
+
+# If you need to set multiple mailbox locations or want to change default
+# namespace settings, you can do it by defining namespace sections.
+#
+# You can have private, shared and public namespaces. Private namespaces
+# are for user's personal mails. Shared namespaces are for accessing other
+# users' mailboxes that have been shared. Public namespaces are for shared
+# mailboxes that are managed by sysadmin. If you create any shared or public
+# namespaces you'll typically want to enable ACL plugin also, otherwise all
+# users can access all the shared mailboxes, assuming they have permissions
+# on filesystem level to do so.
+namespace inbox {
+ # Namespace type: private, shared or public
+ #type = private
+
+ # Hierarchy separator to use. You should use the same separator for all
+ # namespaces or some clients get confused. '/' is usually a good one.
+ # The default however depends on the underlying mail storage format.
+ #separator =
+
+ # Prefix required to access this namespace. This needs to be different for
+ # all namespaces. For example "Public/".
+ #prefix =
+
+ # Physical location of the mailbox. This is in same format as
+ # mail location, which is also the default for it.
+ # mail_driver =
+ # mail_path =
+ #
+ # There can be only one INBOX, and this setting defines which namespace
+ # has it.
+ inbox = yes
+
+ # If namespace is hidden, it's not advertised to clients via NAMESPACE
+ # extension. You'll most likely also want to set list=no. This is mostly
+ # useful when converting from another server with different namespaces which
+ # you want to deprecate but still keep working. For example you can create
+ # hidden namespaces with prefixes "~/mail/", "~%u/mail/" and "mail/".
+ #hidden = no
+
+ # Show the mailboxes under this namespace with LIST command. This makes the
+ # namespace visible for clients that don't support NAMESPACE extension.
+ # "children" value lists child mailboxes, but hides the namespace prefix.
+ #list = yes
+
+ # Namespace handles its own subscriptions. If set to "no", the parent
+ # namespace handles them (empty prefix should always have this as "yes")
+ #subscriptions = yes
+
+ # See 15-mailboxes.conf for definitions of special mailboxes.
+}
+
+# Example shared namespace configuration
+#namespace shared {
+ #type = shared
+ #separator = /
+
+ # Mailboxes are visible under "shared/user@domain/"
+ # $user, $domain and $username are expanded to the destination user.
+ #prefix = shared/$user/
+
+ # Mail location for other users' mailboxes. Note that %{variables} and ~/
+ # expands to the logged in user's data. %{owner_user} and %{owner_home}
+ # destination user's data.
+ #mail_driver = maildir
+ #mail_path = %{owner_home}/Maildir
+ #mail_index_path = ~/Maildir/shared/%{owner_user}
+
+ # Use the default namespace for saving subscriptions.
+ #subscriptions = no
+
+ # List the shared/ namespace only if there are visible shared mailboxes.
+ #list = children
+#}
+# Should shared INBOX be visible as "shared/user" or "shared/user/INBOX"?
+#mail_shared_explicit_inbox = no
+
+# System user and group used to access mails. If you use multiple, userdb
+# can override these by returning uid or gid fields. You can use either numbers
+# or names. <https://doc.dovecot.org/latest/core/config/system_users.html#uids>
+#mail_uid =
+#mail_gid =
+
+# Group to enable temporarily for privileged operations. Currently this is
+# used only with INBOX when either its initial creation or dotlocking fails.
+# Typically this is set to "mail" to give access to /var/mail.
+mail_privileged_group = mail
+
+# Grant access to these supplementary groups for mail processes. Typically
+# these are used to set up access to shared mailboxes. Note that it may be
+# dangerous to set these if users can create symlinks (e.g. if "mail" group is
+# set here, ln -s /var/mail ~/mail/var could allow a user to delete others'
+# mailboxes, or ln -s /secret/shared/box ~/mail/mybox would allow reading it).
+#mail_access_groups =
+
+# Allow full filesystem access to clients. There's no access checks other than
+# what the operating system does for the active UID/GID. It works with both
+# maildir and mboxes, allowing you to prefix mailboxes names with eg. /path/
+# or ~user/.
+#mail_full_filesystem_access = no
+
+# Dictionary for key=value mailbox attributes. This is used for example by
+# URLAUTH and METADATA extensions.
+#mail_attribute {
+# dict file {
+# path = %{home}/Maildir/dovecot-attributes
+# }
+#}
+
+# A comment or note that is associated with the server. This value is
+# accessible for authenticated users through the IMAP METADATA server
+# entry "/shared/comment".
+#mail_server_comment = ""
+
+# Indicates a method for contacting the server administrator. According to
+# RFC 5464, this value MUST be a URI (e.g., a mailto: or tel: URL), but that
+# is currently not enforced. Use for example mailto:admin@example.com. This
+# value is accessible for authenticated users through the IMAP METADATA server
+# entry "/shared/admin".
+#mail_server_admin =
+
+##
+## Mail processes
+##
+
+# Don't use mmap() at all. This is required if you store indexes to shared
+# filesystems (NFS or clustered filesystem).
+#mmap_disable = no
+
+# Rely on O_EXCL to work when creating dotlock files. NFS supports O_EXCL
+# since version 3, so this should be safe to use nowadays by default.
+#dotlock_use_excl = yes
+
+# When to use fsync() or fdatasync() calls:
+# optimized (default): Whenever necessary to avoid losing important data
+# always: Useful with e.g. NFS when write()s are delayed
+# never: Never use it (best performance, but crashes can lose data)
+#mail_fsync = optimized
+
+# Locking method for index files. Alternatives are fcntl, flock and dotlock.
+# Dotlocking uses some tricks which may create more disk I/O than other locking
+# methods. NFS users: flock doesn't work, remember to change mmap_disable.
+#lock_method = fcntl
+
+# Directory where mails can be temporarily stored. Usually it's used only for
+# mails larger than >= 128 kB. It's used by various parts of Dovecot, for
+# example LDA/LMTP while delivering large mails or zlib plugin for keeping
+# uncompressed mails.
+#mail_temp_dir = /tmp
+
+# Valid UID range for users, defaults to 500 and above. This is mostly
+# to make sure that users can't log in as daemons or other system users.
+# Note that denying root logins is hardcoded to dovecot binary and can't
+# be done even if first_valid_uid is set to 0.
+#first_valid_uid = 500
+#last_valid_uid = 0
+
+# Valid GID range for users, defaults to non-root/wheel. Users having
+# non-valid GID as primary group ID aren't allowed to log in. If user
+# belongs to supplementary groups with non-valid GIDs, those groups are
+# not set.
+#first_valid_gid = 1
+#last_valid_gid = 0
+
+# Maximum allowed length for mail keyword name. It's only forced when trying
+# to create new keywords.
+#mail_max_keyword_length = 50
+
+# ':' separated list of directories under which chrooting is allowed for mail
+# processes (ie. /var/mail will allow chrooting to /var/mail/foo/bar too).
+# This setting doesn't affect login_chroot, mail_chroot or auth chroot
+# settings. If this setting is empty, "/./" in home dirs are ignored.
+# WARNING: Never add directories here which local users can modify, that
+# may lead to root exploit. Usually this should be done only if you don't
+# allow shell access for users. <https://doc.dovecot.org/latest/core/config/chrooting.html>
+#valid_chroot_dirs =
+
+# Default chroot directory for mail processes. This can be overridden for
+# specific users in user database by giving /./ in user's home directory
+# (eg. /home/./user chroots into /home). Note that usually there is no real
+# need to do chrooting, Dovecot doesn't allow users to access files outside
+# their mail directory anyway. If your home directories are prefixed with
+# the chroot directory, append "/." to mail_chroot. <doc/wiki/Chrooting.txt>
+#mail_chroot =
+
+# UNIX socket path to master authentication server to find users.
+# This is used by imap (for shared users) and lda.
+#auth_socket_path = /var/run/dovecot/auth-userdb
+
+# Directory where to look up mail plugins.
+#mail_plugin_dir = /usr/lib/dovecot
+
+# Space separated list of plugins to load for all services. Plugins specific to
+# IMAP, LDA, etc. are added to this list in their own .conf files.
+#mail_plugins =
+#
+# To add plugins, use
+#mail_plugins {
+# plugin = yes
+#}
+
+##
+## Mailbox handling optimizations
+##
+
+# Mailbox list indexes can be used to optimize IMAP STATUS commands. They are
+# also required for IMAP NOTIFY extension to be enabled.
+#mailbox_list_index = yes
+
+# Trust mailbox list index to be up-to-date. This reduces disk I/O at the cost
+# of potentially returning out-of-date results after e.g. server crashes.
+# The results will be automatically fixed once the folders are opened.
+#mailbox_list_index_very_dirty_syncs = yes
+
+# Should INBOX be kept up-to-date in the mailbox list index? By default it's
+# not, because most of the mailbox accesses will open INBOX anyway.
+#mailbox_list_index_include_inbox = no
+
+# The minimum number of mails in a mailbox before updates are done to cache
+# file. This allows optimizing Dovecot's behavior to do less disk writes at
+# the cost of more disk reads.
+#mail_cache_min_mail_count = 0
+
+# When IDLE command is running, mailbox is checked once in a while to see if
+# there are any new mails or other changes. This setting defines the minimum
+# time to wait between those checks. Dovecot can also use inotify and
+# kqueue to find out immediately when changes occur.
+#mailbox_idle_check_interval = 30 secs
+
+# Save mails with CR+LF instead of plain LF. This makes sending those mails
+# take less CPU, especially with sendfile() syscall with Linux and FreeBSD.
+# But it also creates a bit more disk I/O which may just make it slower.
+# Also note that if other software reads the mboxes/maildirs, they may handle
+# the extra CRs wrong and cause problems.
+#mail_save_crlf = no
+
+# Max number of mails to keep open and prefetch to memory. This only works with
+# some mailbox formats and/or operating systems.
+#mail_prefetch_count = 0
+
+# How often to scan for stale temporary files and delete them (0 = never).
+# These should exist only after Dovecot dies in the middle of saving mails.
+#mail_temp_scan_interval = 1w
+
+# How many slow mail accesses sorting can perform before it returns failure.
+# With IMAP the reply is: NO [LIMIT] Requested sort would have taken too long.
+# The untagged SORT reply is still returned, but it's likely not correct.
+#mail_sort_max_read_count = 0
+
+protocol !indexer-worker {
+ # If folder vsize calculation requires opening more than this many mails from
+ # disk (i.e. mail sizes aren't in cache already), return failure and finish
+ # the calculation via indexer process. Disabled by default. This setting must
+ # be 0 for indexer-worker processes.
+ #mail_vsize_bg_after_count = 0
+}
+
+##
+## Maildir-specific settings
+##
+
+# By default LIST command returns all entries in maildir beginning with a dot.
+# Enabling this option makes Dovecot return only entries which are directories.
+# This is done by stat()ing each entry, so it causes more disk I/O.
+# (For systems setting struct dirent->d_type, this check is free and it's
+# done always regardless of this setting)
+#maildir_stat_dirs = no
+
+# When copying a message, do it with hard links whenever possible. This makes
+# the performance much better, and it's unlikely to have any side effects.
+#maildir_copy_with_hardlinks = yes
+
+# Assume Dovecot is the only MUA accessing Maildir: Scan cur/ directory only
+# when its mtime changes unexpectedly or when we can't find the mail otherwise.
+#maildir_very_dirty_syncs = no
+
+# If enabled, Dovecot doesn't use the S=<size> in the Maildir filenames for
+# getting the mail's physical size, except when recalculating Maildir++ quota.
+# This can be useful in systems where a lot of the Maildir filenames have a
+# broken size. The performance hit for enabling this is very small.
+#maildir_broken_filename_sizes = no
+
+# Always move mails from new/ directory to cur/, even when the \Recent flags
+# aren't being reset.
+#maildir_empty_new = no
+
+##
+## mbox-specific settings
+##
+
+# Which locking methods to use for locking mbox. There are four available:
+# dotlock: Create <mailbox>.lock file. This is the oldest and most NFS-safe
+# solution. If you want to use /var/mail/ like directory, the users
+# will need write access to that directory.
+# dotlock_try: Same as dotlock, but if it fails because of permissions or
+# because there isn't enough disk space, just skip it.
+# fcntl : Use this if possible. Works with NFS too if lockd is used.
+# flock : May not exist in all systems. Doesn't work with NFS.
+# lockf : May not exist in all systems. Doesn't work with NFS.
+#
+# You can use multiple locking methods; if you do the order they're declared
+# in is important to avoid deadlocks if other MTAs/MUAs are using multiple
+# locking methods as well. Some operating systems don't allow using some of
+# them simultaneously.
+#mbox_read_locks = fcntl
+#mbox_write_locks = dotlock fcntl
+
+# Maximum time to wait for lock (all of them) before aborting.
+#mbox_lock_timeout = 5 mins
+
+# If dotlock exists but the mailbox isn't modified in any way, override the
+# lock file after this much time.
+#mbox_dotlock_change_timeout = 2 mins
+
+# When mbox changes unexpectedly we have to fully read it to find out what
+# changed. If the mbox is large this can take a long time. Since the change
+# is usually just a newly appended mail, it'd be faster to simply read the
+# new mails. If this setting is enabled, Dovecot does this but still safely
+# fallbacks to re-reading the whole mbox file whenever something in mbox isn't
+# how it's expected to be. The only real downside to this setting is that if
+# some other MUA changes message flags, Dovecot doesn't notice it immediately.
+# Note that a full sync is done with SELECT, EXAMINE, EXPUNGE and CHECK
+# commands.
+#mbox_dirty_syncs = yes
+
+# Like mbox_dirty_syncs, but don't do full syncs even with SELECT, EXAMINE,
+# EXPUNGE or CHECK commands. If this is set, mbox_dirty_syncs is ignored.
+#mbox_very_dirty_syncs = no
+
+# Delay writing mbox headers until doing a full write sync (EXPUNGE and CHECK
+# commands and when closing the mailbox). This is especially useful for POP3
+# where clients often delete all mails. The downside is that our changes
+# aren't immediately visible to other MUAs.
+#mbox_lazy_writes = yes
+
+# If mbox size is smaller than this (e.g. 100k), don't write index files.
+# If an index file already exists it's still read, just not updated.
+#mbox_min_index_size = 0
+
+# Mail header selection algorithm to use for MD5 POP3 UIDLs when
+# pop3_uidl_format=%m. For backwards compatibility we use apop3d inspired
+# algorithm, but it fails if the first Received: header isn't unique in all
+# mails. An alternative algorithm is "all" that selects all headers.
+#mbox_md5 = apop3d
+
+##
+## mdbox-specific settings
+##
+
+# Maximum dbox file size until it's rotated.
+#mdbox_rotate_size = 10M
+
+# Maximum dbox file age until it's rotated. Typically in days. Day begins
+# from midnight, so 1d = today, 2d = yesterday, etc. 0 = check disabled.
+#mdbox_rotate_interval = 0
+
+# When creating new mdbox files, immediately preallocate their size to
+# mdbox_rotate_size. This setting currently works only in Linux with some
+# filesystems (ext4, xfs).
+#mdbox_preallocate_space = no
+
+# Settings to control adding $HasAttachment or $HasNoAttachment keywords.
+# By default, all MIME parts with Content-Disposition=attachment, or inlines
+# with filename parameter are consired attachments.
+# add-flags - Add the keywords when saving new mails or when fetching can
+# do it efficiently.
+# content-type=type or !type - Include/exclude content type. Excluding will
+# never consider the matched MIME part as attachment. Including will only
+# negate an exclusion (e.g. content-type=!foo/* content-type=foo/bar).
+# exclude-inlined - Exclude any Content-Disposition=inline MIME part.
+#mail_attachment_detection_options =
--- /dev/null
+#default_process_limit = 100
+#default_client_limit = 1000
+
+# Default VSZ (virtual memory size) limit for service processes. This is mainly
+# intended to catch and kill processes that leak memory before they eat up
+# everything.
+#default_vsz_limit = 256M
+
+# Login user is internally used by login processes. This is the most untrusted
+# user in Dovecot system. It shouldn't have access to anything at all.
+#default_login_user = dovenull
+
+# Internal user is used by unprivileged processes. It should be separate from
+# login user, so that login processes can't disturb other processes.
+#default_internal_user = dovecot
+
+service imap-login {
+ inet_listener imap {
+ #port = 143
+ }
+ inet_listener imaps {
+ #port = 993
+ #ssl = yes
+ }
+
+ # Number of connections to handle before starting a new process. Typically
+ # the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0
+ # is faster. <https://doc.dovecot.org/2.4.1/core/config/service.html#service_restart_request_count>
+ #service_restart_request_count = 1
+
+ # Number of processes to always keep waiting for more connections.
+ #process_min_avail = 0
+
+ # If you set service_restart_request_count=0, you probably need to grow this.
+ #vsz_limit = 256M # default
+}
+
+service pop3-login {
+ inet_listener pop3 {
+ #port = 110
+ }
+ inet_listener pop3s {
+ #port = 995
+ #ssl = yes
+ }
+}
+
+service submission-login {
+ inet_listener submission {
+ #port = 587
+ }
+ inet_listener submissions {
+ #port = 465
+ }
+}
+
+service lmtp {
+ unix_listener lmtp {
+ #mode = 0666
+ }
+
+ # Create inet listener only if you can't use the above UNIX socket
+ #inet_listener lmtp {
+ # Avoid making LMTP visible for the entire internet
+ #listen = 127.0.0.1
+ #port = 24
+ #}
+}
+
+service imap {
+ # Most of the memory goes to mmap()ing files. You may need to increase this
+ # limit if you have huge mailboxes.
+ #vsz_limit = 256M # default
+
+ # Max. number of IMAP processes (connections)
+ #process_limit = 1024
+}
+
+service pop3 {
+ # Max. number of POP3 processes (connections)
+ #process_limit = 1024
+}
+
+service submission {
+ # Max. number of SMTP Submission processes (connections)
+ #process_limit = 1024
+}
+
+service auth {
+ # auth_socket_path points to this userdb socket by default. It's typically
+ # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have
+ # full permissions to this socket are able to get a list of all usernames and
+ # get the results of everyone's userdb lookups.
+ #
+ # The default 0666 mode allows anyone to connect to the socket, but the
+ # userdb lookups will succeed only if the userdb returns an "uid" field that
+ # matches the caller process's UID. Also if caller's uid or gid matches the
+ # socket's uid or gid the lookup succeeds. Anything else causes a failure.
+ #
+ # To give the caller full permissions to lookup all users, set the mode to
+ # something else than 0666 and Dovecot lets the kernel enforce the
+ # permissions (e.g. 0777 allows everyone full permissions).
+ unix_listener auth-userdb {
+ #mode = 0666
+ #user =
+ #group =
+ }
+
+ # Postfix smtp-auth
+ #unix_listener /var/spool/postfix/private/auth {
+ # mode = 0666
+ #}
+
+ # Auth process is run as this user.
+ #user = $SET:default_internal_user
+}
+
+service auth-worker {
+ # Auth worker process is run as root by default, so that it can access
+ # /etc/shadow. If this isn't necessary, the user should be changed to
+ # $SET:default_internal_user.
+ #user = root
+}
+
+service dict {
+ # If dict proxy is used, mail processes should have access to its socket.
+ # For example: mode=0660, group=vmail and global mail_access_groups=vmail
+ unix_listener dict {
+ #mode = 0600
+ #user =
+ #group =
+ }
+}
--- /dev/null
+##
+## Statistics and metrics
+##
+
+# Dovecot supports gathering statistics from events.
+# Currently there are no statistics logged by default, and therefore they must
+# be explicitly added using the metric configuration blocks.
+#
+# Unlike old stats, the new statistics do not require any plugins loaded.
+#
+# See https://doc.dovecot.org/latest/core/config/statistics.html for details
+
+##
+## Example metrics
+##
+
+#metric auth_success {
+# filter = (event=auth_request_finished AND success=yes)
+#}
+#
+#metric auth_failure {
+# filter = (event=auth_request_finished AND NOT success=yes)
+## See exporter config later in this file
+## can be used to replace auth_verbose=yes
+# exporter = log
+#}
+#
+#metric imap_command {
+# filter = event=imap_command_finished
+# group_by cmd_name {
+# method discrete {
+# }
+# }
+# group_by tagged_reply_state {
+# method discrete {
+# }
+# }
+#}
+#
+#metric smtp_command {
+# filter = event=smtp_server_command_finished and protocol=submission
+# group_by cmd_name {
+# method discrete {
+# }
+# }
+# group_by status_code {
+# method discrete {
+# }
+# }
+# group_by duration {
+# method exponential {
+# base = 10
+# min_magnitude = 1
+# max_magnitude = 5
+# }
+# }
+#}
+#
+#metric lmtp_command {
+# filter = event=smtp_server_command_finished and protocol=lmtp
+# group_by cmd_name {
+# method discrete {
+# }
+# }
+# group_by status_code {
+# method discrete {
+# }
+# }
+# group_by duration {
+# method exponential {
+# base = 10
+# min_magnitude = 1
+# max_magnitude = 5
+# }
+# }
+#}
+#
+#metric mail_delivery {
+# filter = event=mail_delivery_finished
+# group_by duration {
+# method exponential {
+# base = 10
+# min_magnitude = 1
+# max_magnitude = 5
+# }
+# }
+#}
+
+##
+## Prometheus
+##
+
+# To allow access to statistics with Prometheus, enable http listener
+# on stats process. Stats will be available on /metrics path.
+#
+# See https://doc.dovecot.org/latest/core/config/statistics.html#openmetrics for more
+# details.
+
+#service stats {
+# inet_listener http {
+# port = 9900
+# }
+#}
+
+##
+## Event exporting
+##
+
+# You can also export individual events.
+#
+# See https://doc.dovecot.org/configuration_manual/event_export/ for more
+# details.
+
+#event_exporter log {
+# format = json
+# time_format = rfc3339
+#}
+#
+#metric imap_commands {
+# exporter = log
+# filter = event=imap_command_finished
+#}
--- /dev/null
+##
+## SSL settings
+##
+
+# SSL/TLS support: yes, no, required. <https://doc.dovecot.org/latest/core/config/ssl.html>
+ssl = yes
+
+# PEM encoded X.509 SSL/TLS certificate and private key. By default, Debian
+# installs a self-signed certificate. This is useful for testing, but you
+# should obtain a real certificate from a recognized certificate authority.
+#
+# These files are opened before dropping root privileges, so keep the key file
+# unreadable by anyone but root. Included /usr/share/dovecot/mkcert.sh can be
+# used to easily generate self-signed certificate, just make sure to update the
+# domains in dovecot-openssl.cnf
+#
+# Preferred permissions: root:root 0444
+ssl_server_cert_file = /etc/dovecot/private/dovecot.pem
+# Preferred permissions: root:root 0400
+ssl_server_key_file = /etc/dovecot/private/dovecot.key
+
+# If key file is password protected, give the password here. Alternatively
+# give it when starting dovecot with -p parameter. Since this file is often
+# world-readable, you may want to place this setting instead to a different
+# root owned 0600 file by using ssl_key_password = <path.
+#ssl_server_key_password =
+
+# PEM encoded trusted certificate authority. Set this only if you intend to use
+# ssl_request_client_cert=yes. The file should contain the CA certificate(s)
+# followed by the matching CRL(s). (e.g. ssl_server_ca_file = /etc/ssl/certs/ca.pem)
+#ssl_server_ca_file =
+
+# Require that CRL check succeeds for client certificates.
+#ssl_server_require_crl = yes
+
+# Request client to send a certificate. If you also want to require it, set
+# auth_ssl_require_client_cert=yes in auth section.
+#ssl_server_request_client_cert = no
+
+# Which field from certificate to use for username. commonName and
+# x500UniqueIdentifier are the usual choices. You'll also need to set
+# auth_ssl_username_from_cert=yes.
+#ssl_server_cert_username_field = commonName
+
+# SSL protocols to use. Debian systems specify TLSv1.2 by default, which should
+# be reasonbly secure and compatible with existing clients.
+ssl_min_protocol = TLSv1.2
+# Diffie-Hellman parameters are no longer required and should be phased out.
+# They do not work with ECDH(E) and require DH(E) ciphers.
+#ssl_server_dh_file = /etc/dovecot/dh.pem
+
+# SSL ciphers to use
+#ssl_cipher_list = ALL:!kRSA:!SRP:!kDHd:!DSS:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!RC4:!ADH:!LOW@STRENGTH
+
+# SSL crypto device to use, for valid values run "openssl engine"
+#ssl_crypto_device = /dev/crypto
--- /dev/null
+##
+## LDA specific settings (also used by LMTP)
+##
+
+# Address to use when sending rejection mails.
+# Default is postmaster@%d. %d expands to recipient domain.
+#postmaster_address =
+
+# Hostname to use in various parts of sent mails (e.g. in Message-Id) and
+# in LMTP replies. Default is the system's real hostname@domain.
+#hostname =
+
+# If user is over quota, return with temporary failure instead of
+# bouncing the mail.
+#quota_full_tempfail = no
+
+# Binary to use for sending mails.
+#sendmail_path = /usr/sbin/sendmail
+
+# If non-empty, send mails via this SMTP host[:port] instead of sendmail.
+#submission_host =
+
+# Subject: header to use for rejection mails. You can use the same variables
+# as for rejection_reason below.
+#rejection_subject = Rejected: %s
+
+# Human readable error message for rejection mails. You can use variables:
+# %n = CRLF, %r = reason, %s = original subject, %t = recipient
+#rejection_reason = Your message to <%t> was automatically rejected:%n%r
+
+# Delimiter character between local-part and detail in email address.
+#recipient_delimiter = +
+
+# Header where the original recipient address (SMTP's RCPT TO: address) is taken
+# from if not available elsewhere. With dovecot-lda -a parameter overrides this.
+# A commonly used header for this is X-Original-To.
+#lda_original_recipient_header =
+
+# Should saving a mail to a nonexistent mailbox automatically create it?
+#lda_mailbox_autocreate = no
+
+# Should automatically created mailboxes be also automatically subscribed?
+#lda_mailbox_autosubscribe = no
+
+protocol lda {
+ # Boolean list of plugins to load
+ #mail_plugins {
+ # sieve = yes
+ #}
+}
--- /dev/null
+##
+## Mailbox definitions
+##
+
+# Each mailbox is specified in a separate mailbox section. The section name
+# specifies the mailbox name. If it has spaces, you can put the name
+# "in quotes". These sections can contain the following mailbox settings:
+#
+# auto:
+# Indicates whether the mailbox with this name is automatically created
+# implicitly when it is first accessed. The user can also be automatically
+# subscribed to the mailbox after creation. The following values are
+# defined for this setting:
+#
+# no - Never created automatically.
+# create - Automatically created, but no automatic subscription.
+# subscribe - Automatically created and subscribed.
+#
+# special_use:
+# A space-separated list of SPECIAL-USE flags (RFC 6154) to use for the
+# mailbox. There are no validity checks, so you could specify anything
+# you want in here, but it's not a good idea to use flags other than the
+# standard ones specified in the RFC:
+#
+# \All - This (virtual) mailbox presents all messages in the
+# user's message store.
+# \Archive - This mailbox is used to archive messages.
+# \Drafts - This mailbox is used to hold draft messages.
+# \Flagged - This (virtual) mailbox presents all messages in the
+# user's message store marked with the IMAP \Flagged flag.
+# \Important - This (virtual) mailbox presents all messages in the
+# user's message store deemed important to user.
+# \Junk - This mailbox is where messages deemed to be junk mail
+# are held.
+# \Sent - This mailbox is used to hold copies of messages that
+# have been sent.
+# \Trash - This mailbox is used to hold messages that have been
+# deleted.
+#
+# comment:
+# Defines a default comment or note associated with the mailbox. This
+# value is accessible through the IMAP METADATA mailbox entries
+# "/shared/comment" and "/private/comment". Users with sufficient
+# privileges can override the default value for entries with a custom
+# value.
+
+# NOTE: Assumes "namespace inbox" has been defined in 10-mail.conf.
+namespace inbox {
+ # These mailboxes are widely used and could perhaps be created automatically:
+ mailbox Drafts {
+ special_use = \Drafts
+ }
+ mailbox Junk {
+ special_use = \Junk
+ }
+ mailbox Trash {
+ special_use = \Trash
+ }
+
+ # For \Sent mailboxes there are two widely used names. We'll mark both of
+ # them as \Sent. User typically deletes one of them if duplicates are created.
+ mailbox Sent {
+ special_use = \Sent
+ }
+ mailbox "Sent Messages" {
+ special_use = \Sent
+ }
+
+ # If you have a virtual "All messages" mailbox:
+ #mailbox virtual/All {
+ # special_use = \All
+ # comment = All my messages
+ #}
+
+ # If you have a virtual "Flagged" mailbox:
+ #mailbox virtual/Flagged {
+ # special_use = \Flagged
+ # comment = All my flagged messages
+ #}
+
+ # If you have a virtual "Important" mailbox:
+ #mailbox virtual/Important {
+ # special_use = \Important
+ # comment = All my important messages
+ #}
+}
--- /dev/null
+##
+## IMAP specific settings
+##
+
+# If nothing happens for this long while client is IDLEing, move the connection
+# to imap-hibernate process and close the old imap process. This saves memory,
+# because connections use very little memory in imap-hibernate process. The
+# downside is that recreating the imap process back uses some resources.
+#imap_hibernate_timeout = 0
+
+# Maximum IMAP command line length. Some clients generate very long command
+# lines with huge mailboxes, so you may need to raise this if you get
+# "Too long argument" or "IMAP command line too large" errors often.
+#imap_max_line_length = 64k
+
+# IMAP logout format string:
+# %{input} - total number of bytes read from client
+# %{output} - total number of bytes sent to client
+# %{fetch_hdr_count} - Number of mails with mail header data sent to client
+# %{fetch_hdr_bytes} - Number of bytes with mail header data sent to client
+# %{fetch_body_count} - Number of mails with mail body data sent to client
+# %{fetch_body_bytes} - Number of bytes with mail body data sent to client
+# %{deleted} - Number of mails where client added \Deleted flag
+# %{expunged} - Number of mails that client expunged, which does not
+# include automatically expunged mails
+# %{autoexpunged} - Number of mails that were automatically expunged after
+# client disconnected
+# %{trashed} - Number of mails that client copied/moved to the
+# special_use=\Trash mailbox.
+# %{appended} - Number of mails saved during the session
+#imap_logout_format = in=%i out=%o deleted=%{deleted} expunged=%{expunged} \
+# trashed=%{trashed} hdr_count=%{fetch_hdr_count} \
+# hdr_bytes=%{fetch_hdr_bytes} body_count=%{fetch_body_count} \
+# body_bytes=%{fetch_body_bytes}
+
+# Amend or override the IMAP capability response. To override, set the value
+# with imap_capability =
+#
+# To amend, you can use a boolean list to specify which capabilities to turn
+# on and off
+#imap_capability {
+# SPECIAL-USE = yes
+# "LITERAL+" = no
+#}
+
+# How long to wait between "OK Still here" notifications when client is
+# IDLEing.
+#imap_idle_notify_interval = 2 mins
+
+# ID field names and values to send to clients. Using * as the value makes
+# Dovecot use the default value. The following fields have default values
+# currently: name, version, os, os-version, support-url, support-email,
+# revision.
+#imap_id_send =
+
+# Use imap_id_received event to log IMAP id
+
+# Workarounds for various client bugs:
+# delay-newmail:
+# Send EXISTS/RECENT new mail notifications only when replying to NOOP
+# and CHECK commands. Some clients ignore them otherwise, for example OSX
+# Mail (<v2.1). Outlook Express breaks more badly though, without this it
+# may show user "Message no longer in server" errors. Note that OE6 still
+# breaks even with this workaround if synchronization is set to
+# "Headers Only".
+# tb-extra-mailbox-sep:
+# Thunderbird gets somehow confused with LAYOUT=fs (mbox and dbox) and
+# adds extra '/' suffixes to mailbox names. This option causes Dovecot to
+# ignore the extra '/' instead of treating it as invalid mailbox name.
+# tb-lsub-flags:
+# Show \Noselect flags for LSUB replies with LAYOUT=fs (e.g. mbox).
+# This makes Thunderbird realize they aren't selectable and show them
+# greyed out, instead of only later giving "not selectable" popup error.
+#
+# This is a boolean list
+#imap_client_workarounds {
+# delay-newmail = yes
+#}
+
+# Host allowed in URLAUTH URLs sent by client. "*" allows all.
+#imap_urlauth_host =
+
+# Enable IMAP LITERAL- extension (replaces LITERAL+)
+#imap_literal_minus = no
+
+# What happens when FETCH fails due to some internal error:
+# disconnect-immediately:
+# The FETCH is aborted immediately and the IMAP client is disconnected.
+# disconnect-after:
+# The FETCH runs for all the requested mails returning as much data as
+# possible. The client is finally disconnected without a tagged reply.
+# no-after:
+# Same as disconnect-after, but tagged NO reply is sent instead of
+# disconnecting the client. If the client attempts to FETCH the same failed
+# mail more than once, the client is disconnected. This is to avoid clients
+# from going into infinite loops trying to FETCH a broken mail.
+#imap_fetch_failure = disconnect-immediately
+
+protocol imap {
+ # Space separated list of plugins to load (default is global mail_plugins).
+ #mail_plugins {
+ # imap_sieve = yes
+ #}
+
+ # Maximum number of IMAP connections allowed for a user from each IP address.
+ # NOTE: The username is compared case-sensitively.
+ #mail_max_userip_connections = 10
+}
--- /dev/null
+##
+#i
+## LMTP specific settings
+##
+
+# Support proxying to other LMTP/SMTP servers by performing passdb lookups.
+#lmtp_proxy = no
+
+# When recipient address includes the detail (e.g. user+detail), try to save
+# the mail to the detail mailbox. See also recipient_delimiter and
+# lda_mailbox_autocreate settings.
+#lmtp_save_to_detail_mailbox = no
+
+# Verify quota before replying to RCPT TO. This adds a small overhead.
+#lmtp_rcpt_check_quota = no
+
+# Add "Received:" header to mails delivered.
+#lmtp_add_received_header = yes
+
+# Which recipient address to use for Delivered-To: header and Received:
+# header. The default is "final", which is the same as the one given to
+# RCPT TO command. "original" uses the address given in RCPT TO's ORCPT
+# parameter, "none" uses nothing. Note that "none" is currently always used
+# when a mail has multiple recipients.
+#lmtp_hdr_delivery_address = final
+
+# Workarounds for various client bugs:
+# whitespace-before-path:
+# Allow one or more spaces or tabs between `MAIL FROM:' and path and between
+# `RCPT TO:' and path.
+# mailbox-for-path:
+# Allow using bare Mailbox syntax (i.e., without <...>) instead of full path
+# syntax.
+#
+#lmtp_client_workarounds {
+# whitespace-before-path = yes
+#}
+
+protocol lmtp {
+ #mail_plugins {
+ # sieve = yes
+ #}
+
+ # This strips the domain name before delivery, since the default
+ # userdb in Debian is /etc/passwd, which doesn't include domain
+ # names in the user. If you're using a different userdb backend
+ # that does include domain names, you may wish to remove this. See
+ # https://doc.dovecot.org/2.4.1/howto/lmtp/exim.html and
+ # https://doc.dovecot.org/2.4.1/core/summaries/settings.html#auth_username_format
+ auth_username_format = %{user | username | lower}
+}
--- /dev/null
+##
+## ManageSieve specific settings
+##
+
+# Uncomment to enable managesieve protocol:
+protocols {
+ sieve = yes
+}
+
+# Service definitions
+
+service managesieve-login {
+ inet_listener sieve {
+ port = 4190
+ }
+
+ inet_listener sieve_deprecated {
+ port = 2000
+ }
+
+ # Number of connections to handle before starting a new process. Typically
+ # the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0
+ # is faster. <https://doc.dovecot.org/latest/core/admin/login_processes.html>
+ #service_restart_request_count = 1
+
+ # Number of processes to always keep waiting for more connections.
+ #process_min_avail = 0
+
+ # If you set service_restart_request_count=0, you probably need to grow this.
+ #vsz_limit = 64M
+}
+
+service managesieve {
+ # Max. number of ManageSieve processes (connections)
+ #process_limit = 1024
+}
+
+# Service configuration
+
+protocol sieve {
+ # Maximum ManageSieve command line length in bytes. ManageSieve usually does
+ # not involve overly long command lines, so this setting will not normally
+ # need adjustment
+ #managesieve_max_line_length = 65536
+
+ # Maximum number of ManageSieve connections allowed for a user from each IP
+ # address.
+ # NOTE: The username is compared case-sensitively.
+ #mail_max_userip_connections = 10
+
+ # Space separated list of plugins to load (none known to be useful so far).
+ # Do NOT try to load IMAP plugins here.
+ #mail_plugins =
+
+ # MANAGESIEVE logout format string:
+ #managesieve_logout_format = bytes=%{input}/%{output}
+
+ # To fool ManageSieve clients that are focused on CMU's timesieved you can
+ # specify the IMPLEMENTATION capability that Dovecot reports to clients.
+ # For example: 'Cyrus timsieved v2.2.13'
+ #managesieve_implementation_string = 'Cyrus timsieved v2.2.13'
+
+ # Explicitly specify the SIEVE and NOTIFY capability reported by the server
+ # before login. If left unassigned these will be reported dynamically
+ # according to what the Sieve interpreter supports by default (after login
+ # this may differ depending on the user).
+ #managesieve_sieve_capability {
+ #}
+ #managesieve_notify_capability {
+ #}
+
+ # The maximum number of compile errors that are returned to the client upon
+ # script upload or script verification.
+ #managesieve_max_compile_errors = 5
+
+ # Refer to 90-sieve.conf for script quota configuration and configuration of
+ # Sieve execution limits.
+}
--- /dev/null
+##
+## POP3 specific settings
+##
+
+# Don't try to set mails non-recent or seen with POP3 sessions. This is
+# mostly intended to reduce disk I/O. With maildir it doesn't move files
+# from new/ to cur/, with mbox it doesn't write Status-header.
+#pop3_no_flag_updates = no
+
+# Support LAST command which exists in old POP3 specs, but has been removed
+# from new ones. Some clients still wish to use this though. Enabling this
+# makes RSET command clear all \Seen flags from messages.
+#pop3_enable_last = no
+
+# If mail has X-UIDL header, use it as the mail's UIDL.
+#pop3_reuse_xuidl = no
+
+# Allow only one POP3 session to run simultaneously for the same user.
+#pop3_lock_session = no
+
+# POP3 requires message sizes to be listed as if they had CR+LF linefeeds.
+# Many POP3 servers violate this by returning the sizes with LF linefeeds,
+# because it's faster to get. When this setting is enabled, Dovecot still
+# tries to do the right thing first, but if that requires opening the
+# message, it fallbacks to the easier (but incorrect) size.
+#pop3_fast_size_lookups = no
+
+# POP3 UIDL (unique mail identifier) format to use. You can use following
+# variables with variable extension as described in
+# https://doc.dovecot.org/latest/core/settings/variables.html
+#
+# %{uidvalidity} - Mailbox's IMAP UIDVALIDITY
+# %{uid} - Mail's IMAP UID
+# %{md5} - MD5 sum of the mailbox headers in hex (mbox only)
+# %{filename} - filename (maildir only)
+# %{guid} - Mail's GUID
+#
+# If you want UIDL compatibility with other POP3 servers, use:
+# UW's ipop3d : %{uid | hex(8)}%{uidvalidity | hex(8)}
+# Courier : %{filename} or %{uidvalidity}-%{uid} (both might be used simultaneously)
+# Cyrus (<= 2.1.3) : %{uid}
+# Cyrus (>= 2.1.4) : %{uidvalidity}.%{uid}
+# Dovecot v0.99.x : %{uidvalidity}.%{uid}
+# tpop3d : %{filename|md5}
+#
+# Note that Outlook 2003 seems to have problems with %{uidvalidity}.%{uid} format which was
+# Dovecot's default, so if you're building a new server it would be a good
+# idea to change this. %{uid | hex(8)}%{uidvalidity | hex(8)} should be pretty fail-safe.
+#
+#pop3_uidl_format = %{uid | hex(8)}%{uidvalidity | hex(8)}
+
+# Permanently save UIDLs sent to POP3 clients, so pop3_uidl_format changes
+# won't change those UIDLs. Currently this works only with Maildir.
+#pop3_save_uidl = no
+
+# What to do about duplicate UIDLs if they exist?
+# allow: Show duplicates to clients.
+# rename: Append a temporary -2, -3, etc. counter after the UIDL.
+#pop3_uidl_duplicates = allow
+
+# This option changes POP3 behavior so that it's not possible to actually
+# delete mails via POP3, only hide them from future POP3 sessions. The mails
+# will still be counted towards user's quota until actually deleted via IMAP.
+# Use e.g. "$POP3Deleted" as the value (it will be visible as IMAP keyword).
+# Make sure you can legally archive mails before enabling this setting.
+#pop3_deleted_flag =
+
+# POP3 logout format string:
+# %{input} - Bytes read from the client
+# %{output} - Bytes sent to the client
+# %{top_count} - Number of TOP commands run
+# %{top_bytes} - Bytes sent to the client because of TOP commands
+# %{retr_count} - Number of RETR commands run
+# %{retr_bytes} - Bytes sent to the client because of RETR commands
+# %{deleted_count} - Number of deleted messages
+# %{deleted_bytes} - Number of bytes in deleted messages
+# %{message_count} - Number of messages before deletion
+# %{message_bytes} - Mailbox size, in bytes, before deletion
+# %{uidl_change} - The old and the new UIDL hash (which can be useful for identifying unexpected changes in UIDLs)
+#pop3_logout_format = top=%{top_count}/%{top_bytes}, retr=%{retr_count}/%{retr_bytes}, del=%{deleted_count}/%{deleted_bytes}, size=%{message_bytes}
+
+# Workarounds for various client bugs:
+# outlook-no-nuls:
+# Outlook and Outlook Express hang if mails contain NUL characters.
+# This setting replaces them with 0x80 character.
+# oe-ns-eoh:
+# Outlook Express and Netscape Mail breaks if end of headers-line is
+# missing. This option simply sends it if it's missing.
+#pop3_client_workarounds {
+# outlook-no-nuls = yes
+#}
+
+protocol pop3 {
+ # Space separated list of plugins to load (default is global mail_plugins).
+ #mail_plugins {
+ #}
+
+ # Maximum number of POP3 connections allowed for a user from each IP address.
+ # NOTE: The username is compared case-sensitively.
+ #mail_max_userip_connections = 10
+}
--- /dev/null
+##
+## Settings specific to SMTP Submission
+##
+
+# SMTP Submission logout format string:
+# %{input} - total number of bytes read from client
+# %{output}- total number of bytes sent to client
+# %{command_count} - Number of commands received from client
+# %{reply_count} - Number of replies sent to client
+# %{session} - Session ID of the login session
+# %{transaction_id} - ID of the current transaction, if any
+#submission_logout_format = in=%{input} out=%{output}
+
+# Host name reported by the SMTP service, for example to the client in the
+# initial greeting and to the relay server in the HELO/EHLO command.
+# Default is the system's real hostname@domain.
+#hostname =
+
+# Maximum size of messages accepted for relay. This announced in the SIZE
+# capability. If not configured, this is either determined from the relay
+# server or left unlimited if no limit is known (relay will reply with error
+# if some unknown limit exists there, which is duly passed to our client).
+#submission_max_mail_size =
+
+# Maximum number of recipients accepted per connection (default: unlimited)
+#submission_max_recipients =
+
+# Workarounds for various client bugs:
+# whitespace-before-path:
+# Allow one or more spaces or tabs between `MAIL FROM:' and path and between
+# `RCPT TO:' and path.
+# mailbox-for-path:
+# Allow using bare Mailbox syntax (i.e., without <...>) instead of full path
+# syntax.
+#
+#submission_client_workarounds {
+# whitespace-before-path = yes
+#}
+
+# Relay server configuration:
+#
+# The Dovecot SMTP submission service directly proxies the mail transaction
+# to the SMTP relay configured here.
+
+# Host name for the relay server (required)
+#submission_relay_host =
+
+# Port for the relay server
+#submission_relay_port = 25
+
+# Is the relay server trusted? This determines whether we try to send
+# (Postfix-specific) XCLIENT data to the relay server
+#submission_relay_trusted = no
+
+# Authentication data for the relay server if authentication is required
+#submission_relay_user =
+#submission_relay_master_user =
+#submission_relay_password =
+
+# SSL configuration for connection to relay server
+#
+# submission_relay_ssl:
+# Indicates whether SSL is used for the connection to the relay server. The
+# following values are defined for this setting:
+#
+# no - No SSL is used
+# smtps - An SMTPS connection (immediate SSL) is used
+# starttls - The STARTTLS command is used to establish SSL layer
+#submission_relay_ssl = no
+
+# submission_relay_ssl_verify:
+# Configures whether the SSL certificate of the relay server is to be
+# verified.
+#submission_relay_ssl_verify = yes
+
+# Write protocol logs for relay connection to this directory for debugging
+#submission_relay_rawlog_dir =
+
+# BURL is configured implicitly by IMAP URLAUTH
+
+# Part of the SMTP capabilities that the submission service can offer to the
+# client (as listed in the EHLO reply) depend on those capabilities also being
+# provided by the relay server. These capabilities currently are:
+#
+# - 8BITMIME
+# - BINARYMIME
+# - DSN
+# - VRFY (always returns 252 without support)
+#
+# By default, the submission service first connects to the relay server to
+# determine the support for such capabilities before sending the initial EHLO
+# reply to the client. If the list of capabilities returned by the relay server
+# is somehow unreliable or it is undesirable to start the connection to the
+# relay server before the first mail transaction is started, the backend
+# capabilities can be configured explicitly using the
+# submission_backend_capabilities setting. This is a space-separated list of
+# SMTP capability names. This setting is only relevant for capabilities that
+# depend on support from the relay server: including (or omitting) capabilities
+# that are not listed above has no effect. When this setting is explicitly set
+# to the empty string, none of the capabilities is enabled. To achieve the
+# default behavior, this setting must be left unconfigured.
+#submission_backend_capabilities =
+
+protocol submission {
+ # Space-separated list of plugins to load (default is global mail_plugins).
+ #mail_plugins {
+ #}
+
+ # Maximum number of SMTP submission connections allowed for a user from
+ # each IP address.
+ # NOTE: The username is compared case-sensitively.
+ #mail_max_userip_connections = 10
+}
+
--- /dev/null
+##
+## Dictionary server settings
+##
+
+# Dictionary can be used to store key=value lists. This is used by several
+# plugins. The dictionary can be accessed either directly or though a
+# dictionary server. The following dict block maps dictionary names to URIs
+# when the server is used. These can then be referenced using URIs in format
+# "proxy::<name>".
+
+dict_server {
+# dict quota {
+# driver = sql
+# sql_driver = mysql
+# hostname = localhost
+#
+# dict_map priv/quota/storage {
+# sql_table = quota
+# username_field = username
+# value_field bytes {
+# type = uint
+# }
+# }
+#
+# dict_map priv/quota/messages {
+# sql_table = quota
+# username_field = username
+# value_field messages {
+# type = uint
+# }
+# }
+# }
+}
+
--- /dev/null
+##
+## Mailbox access control lists.
+##
+
+# vfile backend reads ACLs from "dovecot-acl" file from mail directory.
+# You can also optionally give a global ACL directory path where ACLs are
+# applied to all users' mailboxes. The global ACL directory contains
+# one file for each mailbox, eg. INBOX or sub.mailbox. cache_secs parameter
+# specifies how many seconds to wait between stat()ing dovecot-acl file
+# to see if it changed.
+
+#acl_driver = vfile
+## Deprecated ACL global path
+#acl_global_path = /etc/dovecot/global-acls
+#acl_cache_ttl = 5m
+
+## New inline ACLs
+#mailbox INBOX {
+# acl user=testuser {
+# rights = lri
+# }
+#}
+
+# To let users LIST mailboxes shared by other users, Dovecot needs a
+# shared mailbox dictionary. For example:
+#acl_sharing_map {
+# dict file {
+# path = /var/lib/dovecot/shared-mailboxes
+# }
+#}
--- /dev/null
+mail_plugins {
+ fts = yes
+ fts_flatcurve = yes
+}
+
+fts_autoindex = yes
+
+language_filters = normalizer-icu snowball stopwords
+
+language_tokenizers = generic email-address
+language_tokenizer_generic_algorithm = simple
+
+language en {
+ default = yes
+ filters = lowercase snowball english-possessive stopwords
+}
+
+fts flatcurve {
+ # All of these are optional, and indicate the default values.
+ # They are listed here for documentation purposes; most people should not
+ # need to define/override in their config.
+ # commit_limit = 500
+ # max_term_size = 30
+ # min_term_size = 2
+ # optimize_limit = 10
+ # rotate_count = 5000
+ # rotate_time = 5000
+ substring_search = yes
+}
--- /dev/null
+##
+## Plugin settings
+##
+
+# All wanted plugins must be listed in mail_plugins setting before any of the
+# settings take effect. See <https://doc.dovecot.org/latest/> for list of plugins and
+# their configuration. Note that %{variable} expansion is done for all values.
+
+## See https://doc.dovecot.org/latest/core/plugins/fts.html for FTS information
+
+fts_autoindex = yes
+fts_autoindex_max_recent_msgs = 999
+fts_search_add_missing = yes
+
+## for attachment decoding
+
+#fts_decoder_driver = tika
+#fts_decoder_tika_url = http://localhost:9998/tika/
+
+## for solr
+#fts solr {
+# url = http://localhost:8983/solr/dovecot/
+#}
+
+# See 90-fts-flatcurve.conf in the dovecot-flatcurve package for
+# settings specific to that driver
--- /dev/null
+##
+## Quota configuration.
+##
+
+# Note that you also have to enable quota plugin in mail_plugins setting.
+## <https://doc.dovecot.org/latest/core/plugins/quota.html>
+
+##
+## Quota limits
+##
+
+# Quota limits are set using "quota_rule" parameters. To get per-user quota
+# limits, you can set/override them by returning "quota_rule" extra field
+# from userdb. It's also possible to give mailbox-specific limits, for example
+# to give additional 100 MB when saving to Trash:
+
+#mail_plugins {
+# quota = yes
+#}
+
+#quota "User quota" {
+# storage_size = 1G
+#}
+#
+#namespace inbox {
+# mailbox Trash {
+# quota_storage_extra = 100M
+# }
+#}
+
+##
+## Quota warnings
+##
+
+# You can execute a given command when user exceeds a specified quota limit.
+# Each quota root has separate limits. Only the command for the first
+# exceeded limit is executed, so put the highest limit first.
+# The commands are executed via script service by connecting to the named
+# UNIX socket (quota-warning below).
+# Note that % needs to be escaped as %%, otherwise "% " expands to empty.
+
+#quota "User quota" {
+# warning warn-95 {
+# quota_storage_percentage = 95
+# execute quota-warning {
+# args = 95 %{user}
+# }
+# }
+# warning warn-80 {
+# quota_storage_percentage = 80
+# execute quota-warning {
+# args = 80 %{user}
+# }
+# }
+#}
+
+# Example quota-warning service. The unix listener's permissions should be
+# set in a way that mail processes can connect to it. Below example assumes
+# that mail processes run as vmail user. If you use mode=0666, all system users
+# can generate quota warnings to anyone.
+#service quota-warning {
+# executable = script /usr/local/bin/quota-warning.sh
+# user = dovecot
+# unix_listener quota-warning {
+# user = vmail
+# }
+#}
+
+##
+## Quota backends
+##
+
+# Multiple backends are supported:
+# count: Default and recommended, quota driver tracks the quota internally within Dovecot's index files.
+# maildir: Maildir++ quota
+# fs: Read-only support for filesystem quota
+#quota "User quota" {
+# driver = count
+#}
--- /dev/null
+# Sieve Extprograms plugin configuration
+
+# Don't forget to add the sieve_extprograms plugin to the sieve_plugins setting.
+# Also enable the extensions you need (one or more of vnd.dovecot.pipe,
+# vnd.dovecot.filter and vnd.dovecot.execute) by adding these to the
+# sieve_extensions or sieve_global_extensions settings. Restricting these
+# extensions to a global context using sieve_global_extensions is recommended.
+
+# The directory where the program sockets are located for the
+# vnd.dovecot.pipe, vnd.dovecot.filter and vnd.dovecot.execute extension
+# respectively. The name of each unix socket contained in that directory
+# directly maps to a program-name referenced from the Sieve script.
+#sieve_pipe_socket_dir = sieve-pipe
+#sieve_filter_socket_dir = sieve-filter
+#sieve_execute_socket_dir = sieve-execute
+
+# The directory where the scripts are located for direct execution by the
+# vnd.dovecot.pipe, vnd.dovecot.filter and vnd.dovecot.execute extension
+# respectively. The name of each script contained in that directory
+# directly maps to a program-name referenced from the Sieve script.
+#sieve_pipe_bin_dir = /usr/lib/dovecot/sieve-pipe
+#sieve_filter_bin_dir = /usr/lib/dovecot/sieve-filter
+#sieve_execute_bin_dir = /usr/lib/dovecot/sieve-execute
+
+# An example program service called 'do-something' to pipe messages to
+#service do-something {
+ # Define the executed script as parameter to the sieve service
+ #executable = script /usr/lib/dovecot/sieve-pipe/do-something.sh
+
+ # Use some unprivileged user for executing the program
+ #user = dovenull
+
+ # The unix socket located in the sieve_pipe_socket_dir (as defined in the
+ # plugin {} section above)
+ #unix_listener sieve-pipe/do-something {
+ # LDA/LMTP must have access
+ # user = vmail
+ # mode = 0600
+ #}
+#}
+
--- /dev/null
+##
+## Settings for the Sieve interpreter
+##
+
+# Do not forget to enable the Sieve plugin in 15-lda.conf and 20-lmtp.conf
+# by adding it to the respective mail_plugins { sieve = yes } settings.
+
+# See https://doc.dovecot.org/latest/core/plugins/sieve.html
+
+# Personal sieve script location
+#sieve_script personal {
+# driver = file
+# path = ~/sieve
+# active_path = ~/.dovecot.sieve
+#}
+
+# Default sieve script location
+#sieve_script default {
+# type = default
+# name = default
+# driver = file
+# path = /etc/dovecot/sieve/default/
+#}
+
+
+# Which Sieve language extensions are available to users. By default, all
+# supported extensions are available, except for deprecated extensions or
+# those that are still under development. Some system administrators may want
+# to disable certain Sieve extensions or enable those that are not available
+# by default. This setting can use 'yes' and 'no' to specify differences relative
+# to the default. For example `imapflags = yes' will enable the
+# deprecated imapflags extension in addition to all extensions were already
+# enabled by default.
+#sieve_extensions {
+# mboxmetadata = yes
+# vnd.dovecot.debug = yes
+#}
+
+# Which Sieve language extensions are ONLY available in global scripts. This
+# can be used to restrict the use of certain Sieve extensions to administrator
+# control, for instance when these extensions can cause security concerns.
+# This setting has higher precedence than the `sieve_extensions' setting
+# (above), meaning that the extensions enabled with this setting are never
+# available to the user's personal script no matter what is specified for the
+# `sieve_extensions' setting. The syntax of this setting is similar to the
+# `sieve_extensions' setting, with the difference that extensions are
+# enabled or disabled for exclusive use in global scripts. Currently, no
+# extensions are marked as such by default.
+#sieve_global_extensions =
+
+# The Pigeonhole Sieve interpreter can have plugins of its own. Using this
+# setting, the used plugins can be specified. Check the Dovecot documentation
+# https://doc.dovecot.org/latest/core/plugins/sieve.html
+
+#sieve_plugins = sieve_imapsieve sieve_extprograms
+#sieve_pipe_bin_dir = /usr/share/dovecot-pigeonhole/sieve
+#sieve_execute_bin_dir = /usr/share/dovecot-pigeonhole/sieve
+#sieve_global_extensions {
+# vnd.dovecot.pipe = yes
+# vnd.dovecot.execute = yes
+#}
+#imapsieve_url =
+
+# The separator that is expected between the :user and :detail
+# address parts introduced by the subaddress extension. This may
+# also be a sequence of characters (e.g. '--'). The current
+# implementation looks for the separator from the left of the
+# localpart and uses the first one encountered. The :user part is
+# left of the separator and the :detail part is right. This setting
+# is also used by Dovecot's LMTP service.
+#recipient_delimiter = +-_
+
+# The maximum size of a Sieve script. The compiler will refuse to compile any
+# script larger than this limit. If set to 0, no limit on the script size is
+# enforced.
+#sieve_max_script_size = 1M
+
+# The maximum number of actions that can be performed during a single script
+# execution. If set to 0, no limit on the total number of actions is enforced.
+#sieve_max_actions = 32
+
+# The maximum number of redirect actions that can be performed during a single
+# script execution. If set to 0, no redirect actions are allowed.
+#sieve_max_redirects = 4
+
+# The maximum number of personal Sieve scripts a single user can have. If set
+# to 0, no limit on the number of scripts is enforced.
+# (Currently only relevant for ManageSieve)
+#sieve_quota_script_count = 0
+
+# The maximum amount of disk storage a single user's scripts may occupy. If
+# set to 0, no limit on the used amount of disk storage is enforced.
+# (Currently only relevant for ManageSieve)
+#sieve_quota_storage_size = 0
+
+
+#mailbox Spam {
+## From elsewhere to Spam folder
+# sieve_script report-spam {
+# type = before
+# cause = copy
+# path = /etc/dovecot/report-spam.sieve
+# }
+#}
+
+## From Spam folder to elsewhere
+#imapsieve_from Spam {
+# sieve_script report-ham {
+# type = before
+# cause = copy
+# path = /etc/dovecot/report-ham.sieve
+# }
+#}
--- /dev/null
+# Deny access for users. Included from auth.conf.
+
+# Users can be (temporarily) disabled by adding a passdb with deny=yes.
+# If the user is found from that database, authentication will fail.
+# The deny passdb should always be specified before others, so it gets
+# checked first.
+
+# Example deny passdb using passwd-file. You can use any passdb though.
+#passdb deny {
+# driver = passwd-file
+# deny = yes
+#
+## File contains a list of usernames, one per line
+# passwd_file_path = /etc/dovecot/deny-users
+#}
--- /dev/null
+# Authentication for LDAP users. Included from auth.conf.
+#
+# <https://doc.dovecot.org/latest/core/config/auth/databases/ldap.html>
+
+## See <https://doc.dovecot.org/latest/core/config/dict.html#ldap>
+
+#ldap_uris = ldap://localhost
+#ldap_auth_dn = cn=admin
+#ldap_auth_dn_password = supersecret
+
+#passdb ldap {
+# ldap_filter = (&(objectClass=posixAccount)(uid=%{user}))
+# ldap_bind = no
+
+# fields {
+ # user=%{ldap:uid}
+ # password=%{ldap:userPassword}
+ # userdb_home=%{ldap:homeDirectory}
+ # userdb_uid=%{ldap:uidNumber}
+ # userdb_gid=%{ldap:gidNumber}
+# }
+#}
+
+# "prefetch" user database means that the passdb already provided the
+# needed information and there's no need to do a separate userdb lookup.
+# <https://doc.dovecot.org/latest/core/config/auth/databases/prefetch.html>
+#userdb prefetch {
+#}
+
+#userdb ldap {
+# ldap_filter = (&(objectClass=posixAccount)(uid=%{user}))
+
+# Default fields can be used to specify defaults that LDAP may override
+# fields {
+# home=/home/virtual/%{user}
+# }
+#}
+
+# If you don't have any user-specific settings, you can avoid the userdb LDAP
+# lookup by using userdb static instead of userdb ldap, for example:
+# <https://doc.dovecot.org/latest/core/config/auth/databases/static.html>
+#userdb static {
+ #fields {
+ # uid = vmail
+ # gid = vmail
+ # home = /var/vmail/%{user}
+ #}
+#}
--- /dev/null
+# Authentication for master users. Included from auth.conf.
+
+# By adding master=yes setting inside a passdb you make the passdb a list
+# of "master users", who can log in as anyone else.
+# <https://doc.dovecot.org/latest/core/config/auth/master_users.html>
+
+# Example master user passdb using passwd-file. You can use any passdb though.
+#passdb master-passwd-file {
+# driver = passwd-file
+# master = yes
+# passwd_file_path = /etc/dovecot/master-users
+#}
--- /dev/null
+#auth_mechanisms {
+# xoauth2 = yes
+# oauthbearer = yes
+#}
+#oauth2 {
+# tokeninfo_url = https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=
+# introspection_url = https://www.googleapis.com/oauth2/v2/userinfo
+# #force_introspection = yes
+# username_attribute = email
+#}
+
+# with local validation
+#oauth2 {
+# introspection_mode = local
+# username_attribute = email
+# oauth2_local_validation {
+# dict fs {
+# fs posix {
+# prefix = /etc/dovecot/oauth2-keys/
+# }
+# }
+# }
+#}
--- /dev/null
+# Authentication for passwd-file users. Included from auth.conf.
+#
+# passwd-like file with specified location.
+# <https://doc.dovecot.org/latest/core/config/auth/databases/passwd_file.html>
+
+#passdb passwd-file {
+# default_password_scheme = crypt
+# auth_username_format = %{user}
+# passwd_file_path = /etc/dovecot/users
+#}
+
+#userdb passwd-file {
+# auth_username_format = %{user}
+# passwd_file_path = /etc/dovecot/users
+
+# fields {
+# quota_rule:default=*:storage=1G
+# home=/home/virtual/%{user}
+# }
+#}
--- /dev/null
+# Authentication for SQL users. Included from auth.conf.
+#
+# <https://doc.dovecot.org/latest/core/config/auth/databases/sql.html>
+
+# For the sql passdb module, you'll need a database with a table that
+# contains fields for at least the username and password. If you want to
+# use the user@domain syntax, you might want to have a separate domain
+# field as well.
+#
+# If your users all have the same uig/gid, and have predictable home
+# directories, you can use the static userdb module to generate the home
+# dir based on the username and domain. In this case, you won't need fields
+# for home, uid, or gid in the database.
+#
+# If you prefer to use the sql userdb module, you'll want to add fields
+# for home, uid, and gid. Here is an example table:
+#
+# CREATE TABLE users (
+# username VARCHAR(128) NOT NULL,
+# domain VARCHAR(128) NOT NULL,
+# password VARCHAR(64) NOT NULL,
+# home VARCHAR(255) NOT NULL,
+# uid INTEGER NOT NULL,
+# gid INTEGER NOT NULL,
+# active CHAR(1) DEFAULT 'Y' NOT NULL
+# );
+
+# Database driver: mysql, pgsql, sqlite
+#sql_driver = mysql
+
+# Database connection string. This is driver-specific setting.
+#
+# HA / round-robin load-balancing is supported by giving multiple host
+# settings, like: host=sql1.host.org host=sql2.host.org
+#
+# pgsql:
+# For available options, see the PostgreSQL documention for the
+# PQconnectdb function of libpq.
+# Use maxconns=n (default 5) to change how many connections Dovecot can
+# create to pgsql.
+#
+# mysql:
+# Basic options emulate PostgreSQL option names:
+# host, port, user, password, dbname
+#
+# But also adds some new settings:
+# client_flags - See MySQL manual
+# ssl_ca, ssl_ca_path - Set either one or both to enable SSL
+# ssl_cert, ssl_key - For sending client-side certificates to server
+# ssl_cipher - Set minimum allowed cipher security (default: HIGH)
+# option_file - Read options from the given file instead of
+# the default my.cnf location
+# option_group - Read options from the given group (default: client)
+#
+# You can connect to UNIX sockets by using host: host=/var/run/mysql.sock
+# Note that currently you can't use spaces in parameters.
+#
+# sqlite:
+# The path to the database file.
+#
+# Examples:
+# mysql 192.168.1.1 {
+# dbname = users
+# }
+# mysql sql.example.com {
+# ssl = yes
+# user = virtual
+# password = blarg
+# dbname = virtual
+# }
+# sqlite /etc/dovecot/authdb.sqlite {
+# }
+#
+#mysql /var/run/mysqld/mysqld.sock {
+# user = dovecot
+# password = dvmail
+# dbname = dovecot
+#}
+#mysql localhost {
+# ...
+#}
+
+#passdb sql {
+# default_password_scheme = SHA256
+
+# passdb query to retrieve the password. It can return fields:
+# password - The user's password. This field must be returned.
+# user - user@domain from the database. Needed with case-insensitive lookups.
+# username and domain - An alternative way to represent the "user" field.
+#
+# The "user" field is often necessary with case-insensitive lookups to avoid
+# e.g. "name" and "nAme" logins creating two different mail directories. If
+# your user and domain names are in separate fields, you can return "username"
+# and "domain" fields instead of "user".
+#
+# The query can also return other fields which have a special meaning, see
+# https://doc.dovecot.org/latest/core/config/auth/passdb.html#extra-fields
+#
+# Commonly used available substitutions (see https://doc.dovecot.org/latest/core/settings/variables.html
+# for full list):
+# %{user} = entire user@domain
+# %{user|username} = user part of user@domain
+# %{user|domain} = domain part of user@domain
+#
+# Note that these can be used only as input to SQL query. If the query outputs
+# any of these substitutions, they're not touched. Otherwise it would be
+# difficult to have eg. usernames containing '%' characters.
+#
+# Example:
+# query = SELECT userid AS user, pw AS password \
+# FROM users WHERE userid = '%u' AND active = 'Y'
+#
+# query = \
+# SELECT userid as username, domain, password \
+# FROM users WHERE userid = '%{user|username}' AND domain = '%{user|domain}'
+#}
+
+#userdb sql {
+# userdb query to retrieve the user information. It can return fields:
+# uid - System UID (overrides mail_uid setting)
+# gid - System GID (overrides mail_gid setting)
+# home - Home directory
+# mail_driver - Mail driver
+# mail_path - Mail storage path
+#
+# None of these are strictly required. If you use a single UID and GID, and
+# home or mail directory fits to a template string, you could use userdb static
+# instead. For a list of all fields that can be returned, see
+# Examples:
+# query = SELECT home, uid, gid FROM users WHERE userid = '%{user}'
+# query = SELECT dir AS home, user AS uid, group AS gid FROM users where userid = '%{user}'
+# query = SELECT home, 501 AS uid, 501 AS gid FROM users WHERE userid = '%{user}'
+#
+# query = \
+# SELECT home, uid, gid \
+# FROM users WHERE userid = '%{user|username}' AND domain = '%{user|domain}'
+
+# Query to get a list of all usernames.
+# iterate_query = SELECT username AS user,domain FROM users
+
+# userdb_ldap {
+# iterate_fields {
+# home = /var/vmail/%{home}
+# }
+# }
+#}
+
+#passdb static {
+# fields {
+# user=%{user|username|lower}
+# noauthenticate=yes
+# }
+## you can remove next line if you want to always normalize your usernames
+# skip = authenticated
+#}
+
+# "prefetch" user database means that the passdb already provided the
+# needed information and there's no need to do a separate userdb lookup.
+# <https://doc.dovecot.org/latest/core/config/auth/databases/prefetch.html>
+#userdb prefetch {
+#}
+
+#userdb static {
+# fields {
+# user=%{user|lower}
+# }
+# you can remove next line if you want to always normalize your usernames
+# skip = found
+#}
+
+# If you don't have any user-specific settings, you can avoid the user_query
+# by using userdb static instead of userdb sql, for example:
+# <https://doc.dovecot.org/latest/core/config/auth/databases/static.html>
+#userdb static {
+ #fields {
+ # uid = vmail
+ # gid = vmail
+ # home = /var/vmail/%{user}
+ #}
+#}
--- /dev/null
+# Static passdb. Included from auth.conf.
+
+# This can be used for situations where Dovecot doesn't need to verify the
+# username or the password, or if there is a single password for all users:
+#
+# - proxy frontend, where the backend verifies the password
+# - proxy backend, where the frontend already verified the password
+# - authentication with SSL certificates
+# - simple testing
+#
+# Each passdb must have unique name, but if you onlu use it once per driver
+# you can use driver name as passdb/userdb section name.
+
+#passdb static {
+# fields {
+# proxy = y
+# host = %{user|md5|substr(1)}.example.com
+# nopassword = y
+# }
+#}
+
+#passdb static2 {
+# driver = static
+# fields {
+# password = test
+# }
+#}
+
+#userdb static {
+# fields {
+# uid = vmail
+# gid = vmail
+# home = /home/%{user}
+# }
+#}
--- /dev/null
+# Authentication for system users. Included from auth.conf.
+#
+# <https://doc.dovecot.org/latest/core/config/auth/passdb.html>
+# <https://doc.dovecot.org/latest/core/config/auth/userdb.html>
+
+# Driver is only needed if the section name is not same as driver's name.
+
+# PAM authentication. Preferred nowadays by most systems.
+# PAM is typically used with either userdb passwd or userdb static.
+# REMEMBER: You'll need /etc/pam.d/dovecot file created for PAM
+# authentication to actually work. <https://doc.dovecot.org/latest/core/config/auth/databases/pam.html>
+passdb pam {
+# driver = pam
+# session = yes
+# setcred = yes
+# failure_show_msg = yes
+# max_requests = 20
+# service_name = dovecot
+# skip = authenticated
+# fields {
+# }
+}
+
+userdb passwd {
+}
+
+# System users (NSS, /etc/passwd, or similar).
+# In many systems nowadays this uses Name Service Switch, which is
+# configured in /etc/nsswitch.conf. <https://doc.dovecot.org/latest/core/config/auth/databases/passwd.html>
+#passdb passwb {
+#}
+
+# PAM-like authentication for OpenBSD.
+# <https://doc.dovecot.org/latest/core/config/auth/databases/bsd.html>
+#passdb bsdauth {
+#}
+
+##
+## User databases
+##
+
+# System users (NSS, /etc/passwd, or similiar). In many systems nowadays this
+# uses Name Service Switch, which is configured in /etc/nsswitch.conf.
+#userdb passwd-file {
+ #driver = passwd-file
+ #auth_username_format=%{user|lower}
+ #passwd_file_path = /etc/passwd
+ #fields {
+ # user= %{user|lower}
+ # name = %{user|lower}
+ # home = /var/vmail/%{user}
+ #}
+ #skip = found
+#}
+
+# Static settings generated from template <https://doc.dovecot.org/latest/core/config/auth/databases/static.html>
+#userdb static {
+ #driver = static
+ # Can return anything a userdb could normally return. For example:
+ # fields {
+ # uid = 500
+ # gid = 500
+ # home = /var/mail/%{user}
+ # }
+ # LDA and LMTP needs to look up users only from the userdb. This of course
+ # doesn't work with static userdb because there is no list of users.
+ # Normally static userdb handles this by doing a passdb lookup. This works
+ # with most passdbs, with PAM being the most notable exception. If you do
+ # the user verification another way, you can add allow_all_users=yes
+ # in which case the passdb lookup is skipped.
+ #allow_all_users = yes
+#}
--- /dev/null
+## Dovecot configuration file
+
+# If you're in a hurry, see https://doc.dovecot.org/latest/core/config/guides/quick.html
+
+# "doveconf -n" command gives a clean output of the changed settings. Use it
+# instead of copy&pasting files when posting to the Dovecot mailing list.
+
+# '#' character and everything after it is treated as comments. Extra spaces
+# and tabs are ignored. If you want to use either of these explicitly, put the
+# value inside quotes, eg.: key = "# char and trailing whitespace "
+
+# Default values are shown for each setting, it's not required to uncomment
+# those. These are exceptions to this though: No sections (e.g. namespace {})
+# or plugin settings are added by default, they're listed only as examples.
+# Paths are also just examples with the real defaults being based on configure
+# options. The paths listed here are for configure --prefix=/usr/local
+# --sysconfdir=/usr/local/etc --localstatedir=/var
+
+dovecot_config_version = 2.4.0
+dovecot_storage_version = 2.4.0
+
+# Protocols we want to be serving.
+#protocols = imap pop3 lmtp
+!include_try /usr/share/dovecot/protocols.d/*.protocol
+
+# A comma separated list of IPs or hosts where to listen in for connections.
+# "*" listens in all IPv4 interfaces, "::" listens in all IPv6 interfaces.
+# If you want to specify non-default ports or anything more complex,
+# edit conf.d/master.conf.
+#listen = *, ::
+
+# Base directory where to store runtime data.
+#base_dir = /var/run/dovecot/
+
+# Name of this instance. In multi-instance setup doveadm and other commands
+# can use -i <instance_name> to select which instance is used (an alternative
+# to -c <config_path>). The instance name is also added to Dovecot processes
+# in ps output.
+#instance_name = dovecot
+
+# Greeting message for clients.
+#login_greeting = Dovecot ready.
+
+# Space separated list of trusted network ranges. Connections from these
+# IPs are allowed to override their IP addresses and ports (for logging and
+# for authentication checks). disable_plaintext_auth is also ignored for
+# these networks, unless ssl=required.
+# Typically you'd specify your IMAP proxy servers here.
+#login_trusted_networks =
+
+# With proxy_maybe=yes if proxy destination matches any of these IPs, don't do
+# proxying. This isn't necessary normally, but may be useful if the destination
+# IP is e.g. a load balancer's IP.
+#auth_proxy_self =
+
+# Show more verbose process titles (in ps). Currently shows user name and
+# IP address. Useful for seeing who are actually using the IMAP processes
+# (eg. shared mailboxes or if same uid is used for multiple accounts).
+#verbose_proctitle = yes
+
+# Should all processes be killed when Dovecot master process shuts down.
+# Setting this to "no" means that Dovecot can be upgraded without
+# forcing existing client connections to close (although that could also be
+# a problem if the upgrade is e.g. because of a security fix).
+#shutdown_clients = yes
+
+# If non-zero, run mail commands via this many connections to doveadm server,
+# instead of running them directly in the same process.
+#doveadm_worker_count = 0
+# UNIX socket or host:port used for connecting to doveadm server
+#doveadm_socket_path = doveadm-server
+
+# Space separated list of environment variables that are preserved on Dovecot
+# startup and passed down to all of its child processes. You can also give
+# key=value pairs to always set specific settings.
+#import_environment {
+# TZ=%{env:TZ}
+#}
+
+# Most of the actual configuration gets included below. The filenames are
+# first sorted by their ASCII value and parsed in that order. The 00-prefixes
+# in filenames are intended to make it easier to understand the ordering.
+!include conf.d/*.conf
+
+# A config file can also tried to be included without giving an error if
+# it's not found:
+!include_try local.conf
--- /dev/null
+Source: dovecot
+Section: mail
+Priority: optional
+Maintainer: Dovecot Maintainers <dovecot@packages.debian.org>
+Uploaders: Noah Meyerhans <noahm@debian.org>
+Build-Depends: debhelper-compat (= 13),
+ default-libmysqlclient-dev,
+ krb5-multidev,
+ libapparmor-dev [linux-any],
+ libbz2-dev,
+ libcap-dev [linux-any],
+ libcdb-dev,
+ libdb-dev,
+ libexpat-dev,
+ libexttextcat-dev,
+ libicu-dev,
+ libldap2-dev,
+ liblua5.4-dev,
+ liblz4-dev,
+ liblzma-dev,
+ libpam0g-dev,
+ libpq-dev,
+ libsasl2-dev,
+ libsodium-dev,
+ libsqlite3-dev,
+ libssl-dev,
+ libstemmer-dev,
+ libsystemd-dev [linux-any],
+ libtirpc-dev,
+ libunwind-dev,
+ libwrap0-dev,
+ libxapian-dev,
+ libzstd-dev,
+ lsb-release,
+ lua-json <!nocheck>,
+ pkgconf,
+ zlib1g-dev
+Standards-Version: 4.7.0
+Rules-Requires-Root: no
+Homepage: https://dovecot.org/
+Vcs-Git: https://salsa.debian.org/debian/dovecot.git
+Vcs-Browser: https://salsa.debian.org/debian/dovecot
+
+Package: dovecot-core
+Architecture: any
+Pre-Depends: ${misc:Pre-Depends}
+Depends: adduser,
+ libpam-runtime,
+ openssl,
+ ssl-cert,
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Suggests: dovecot-flatcurve,
+ dovecot-gssapi,
+ dovecot-imapd,
+ dovecot-ldap,
+ dovecot-lmtpd,
+ dovecot-managesieved,
+ dovecot-mysql,
+ dovecot-pgsql,
+ dovecot-pop3d,
+ dovecot-sieve,
+ dovecot-solr,
+ dovecot-sqlite,
+ dovecot-submissiond,
+ ntp
+Provides: dovecot-abi-${dovecot:ABI-Version}, dovecot-common
+Replaces: dovecot-common (<< 1:2.0.14-2~), mailavenger (<< 0.8.1-4)
+Breaks: dovecot-common (<< 1:2.0.14-2~), mailavenger (<< 0.8.1-4)
+Description: secure POP3/IMAP server - core files
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package contains the Dovecot main server and its command line utility.
+
+Package: dovecot-dev
+Architecture: any
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Replaces: dovecot-common (<< 1:2.0.14-2~), dovecot-core (<< 1:2.2.26.0-1~)
+Breaks: dovecot-common (<< 1:2.0.14-2~), dovecot-core (<< 1:2.2.26.0-1~)
+Description: secure POP3/IMAP server - header files
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package contains header files needed to compile plugins for the Dovecot
+ mail server.
+
+Package: dovecot-imapd
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}),
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Suggests: ufw
+Provides: imap-server
+Description: secure POP3/IMAP server - IMAP daemon
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package contains the Dovecot IMAP server.
+
+Package: dovecot-pop3d
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}),
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Suggests: ufw
+Provides: pop3-server
+Description: secure POP3/IMAP server - POP3 daemon
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package contains the Dovecot POP3 server.
+
+Package: dovecot-lmtpd
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}),
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Replaces: dovecot-common (<< 1:2.0.14-2~)
+Breaks: dovecot-common (<< 1:2.0.14-2~)
+Description: secure POP3/IMAP server - LMTP server
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package contains the Dovecot LMTP server.
+
+Package: dovecot-managesieved
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}),
+ dovecot-sieve (= ${binary:Version}),
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Replaces: dovecot-common (<< 1:2.0.14-2~)
+Breaks: dovecot-common (<< 1:2.0.14-2~)
+Description: secure POP3/IMAP server - ManageSieve server
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package contains the Dovecot ManageSieve server.
+
+Package: dovecot-pgsql
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends}
+Description: secure POP3/IMAP server - PostgreSQL support
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package provides PostgreSQL support for Dovecot.
+
+Package: dovecot-mysql
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends}
+Description: secure POP3/IMAP server - MySQL support
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package provides MySQL support for Dovecot.
+
+Package: dovecot-sqlite
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends}
+Description: secure POP3/IMAP server - SQLite support
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package provides SQLite support for Dovecot.
+
+Package: dovecot-ldap
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}),
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Replaces: dovecot-core (<< 1:2.2.26.0-1~)
+Breaks: dovecot-core (<< 1:2.2.26.0-1~)
+Description: secure POP3/IMAP server - LDAP support
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package provides LDAP support for Dovecot.
+
+Package: dovecot-gssapi
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends}
+Description: secure POP3/IMAP server - GSSAPI support
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package provides GSSAPI authentication support for Dovecot.
+
+Package: dovecot-sieve
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}),
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Breaks: dovecot-core (<= 1:2.2.13-6), dovecot-imapd (<< 1:2.2.26.0-1~)
+Replaces: dovecot-core (<= 1:2.2.13-6), dovecot-imapd (<< 1:2.2.26.0-1~)
+Description: secure POP3/IMAP server - Sieve filters support
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package provides Sieve filters support for Dovecot.
+
+Package: dovecot-flatcurve
+Architecture: any
+Conflicts: dovecot-fts-flatcurve
+Replaces: dovecot-fts-flatcurve
+Depends: dovecot-core (= ${binary:Version}),
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: secure POP3/IMAP server - Flatcurve support
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package provides Flatcurve full text search support for Dovecot.
+
+Package: dovecot-solr
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends}
+Description: secure POP3/IMAP server - Solr support
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package provides Solr full text search support for Dovecot.
+
+Package: dovecot-submissiond
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}),
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Breaks: dovecot-submission (<< 1:2.3.1-2~)
+Replaces: dovecot-submission (<< 1:2.3.1-2~)
+Description: secure POP3/IMAP server - mail submission agent
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package contains the Dovecot Mail Submission Agent which implements a
+ basic SMTP submission service with BURL support.
+
+Package: dovecot-auth-lua
+Architecture: any
+Depends: dovecot-core (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends}
+Description: secure POP3/IMAP server - Lua authentication plugin
+ Dovecot is a mail server whose major goals are security and extreme
+ reliability. It tries very hard to handle all error conditions and verify
+ that all data is valid, making it nearly impossible to crash. It supports
+ mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
+ fast, extensible, and portable.
+ .
+ This package contains an authentication plugin allowing password and user
+ databases to be implemented in Lua.
--- /dev/null
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: dovecot
+Upstream-Contact: Timo Sirainen <tss@iki.fi>
+ Dovecot Mailing List <dovecot@dovecot.org>
+Comment: This package was debianized by Jaldhar H. Vyas <jaldhar@debian.org> on
+ Tue, 3 Dec 2002 01:10:07 -0500.
+Source: https://github.com/dovecot/core
+Files-Excluded-pigeonhole: doc/rfc
+
+Files: *
+Copyright: (c) 2001-2022 Dovecot authors
+Comment: see AUTHORS
+License: LGPL-2.1
+
+Files: src/lib/*
+ src/lib-sql/*
+Copyright: (c) 2001-2022 Dovecot authors
+Comment: see AUTHORS
+License: MIT
+
+Files: src/auth/*
+Copyright: (c) 2004-2006 Andrey Panin <pazke@donpac.ru>
+ (c) 2005 Jelmer Vernooij <jelmer@samba.org>
+ (c) 2011-2016 Florian Zeitz <florob@babelmonkeys.de>
+ (c) 2007 Dmitry Butskoy <dmitry@butskoy.name>
+ (C) 2006 Simon L Jackson
+ (c) 2015-2017 Dovecot Oy
+License: MIT
+
+Files: src/lib-auth/password-scheme-md5crypt.c
+Copyright: Poul-Henning Kamp <phk@login.dknet.dk>
+License: Beerware
+ "THE BEER-WARE LICENSE" (Revision 42):
+ <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
+ can do whatever you want with this stuff. If we meet some day, and you think
+ this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+
+Files: src/lib-otp/*
+Copyright: (c) 2004-2006 Andrey Panin <pazke@donpac.ru>
+ (c) Copyright 1997-2005 Simon Tatham
+License: MIT
+
+Files: src/lib/md5.c
+Copyright: N/A
+License: public-domain-solardiz
+ Written by Solar Designer <solar@openwall.com> in 2001, and placed in the
+ public domain. There's absolutely no warrant.
+
+Files: src/lib/sha1.*
+Copyright: Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+License: BSD-3-Clause
+
+Files: src/lib/sha2.* src/lib/sha3.*
+Copyright: Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch>
+License: BSD-3-Clause
+
+Files: src/lib/byteorder.h src/lib/test-byteorder.c
+Copyright: (c) 2016-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+License: MIT
+
+Files: src/lib/hmac.*
+Copyright: (c) 2004 Andrey Panin <pazke@donpac.ru>
+ (c) 2011-2016 Florian Zeitz <florob@babelmonkeys.de>
+License: MIT
+
+Files: src/lib/hmac-cram-md5.c
+Copyright: (c) 2003 Joshua Goodall <joshua@roughtrade.net>
+License: MIT
+
+Files: src/lib/ioloop-iolist.c
+Copyright: (c) 2004 Andrey Panin <pazke@donpac.ru>
+License: MIT
+
+Files: src/lib/ioloop-kqueue.c src/lib/ioloop-notify-kqueue.c
+Copyright: (c) 2005 Vaclav Haisman <v.haisman@sh.cvut.cz>
+License: MIT
+
+Files: src/lib/UnicodeData.txt
+Copyright: Copyright (C) 1991-2007 Unicode, Inc.
+License: Unicode-DFS-2015
+
+Files: src/lib-language/stopwords/*
+Copyright: Copyright (c) 2001, Dr Martin Porter
+ Copyright (c) 2002, Richard Boulton.
+Comment: License obtained from http://snowball.tartarus.org/license.html
+License: BSD-3-Clause
+
+Files: src/lib-language/stopwords/stopwords_en.txt
+Copyright: Apache Software Foundation
+License: Apache-2.0
+
+Files: src/lib-language/stopwords/stopwords_ro.txt
+Copyright: (c) 2005, Jacques Savoy.
+License: BSD-2-Clause
+
+Files: debian/*
+Copyright: (c) 2006-2016 Jaldhar H. Vyas <jaldhar@debian.org>
+ (c) 2006-2009 Fabio Tranchitella <kobold@debian.org>
+ (c) 2008-2009 Joel Johnson <mrjoel@lixil.net>
+ (c) 2009-2012 Marco Nenciarini <mnencia@debian.org>
+ (c) 2011-2013 Micah Anderson <micah@debian.org>
+ (c) 2014-2015 Jelmer Vernooij <jelmer@debian.org>
+ (c) 2016-2018 Apollon Oikonomopoulos <apoikos@debian.org>
+ (c) 2020-2022 Noah Meyerhans <noahm@debian.org>
+ (c) 2020-2022 Christian Göttsche <cgzones@googlemail.com>
+License: GPL-2+
+
+Files: pigeonhole/*
+Copyright: (c) 2002-2021 Stephan Bosch <stephan@rename-it.nl>
+ (c) 2002-2021 Dovecot authors
+Comment: Source obtained from https://pigeonhole.dovecot.org/
+License: LGPL-2.1
+
+License: MIT
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+License: LGPL-2.1
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU Lesser General Public
+ License along with this program. If not, see
+ <https://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the full text of the GNU Lesser General Public License can
+ be found at /usr/share/common-licenses/LGPL-2.1.
+
+License: Apache-2.0
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ .
+ http://www.apache.org/licenses/LICENSE-2.0
+ .
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ .
+ On Debian systems, the full text of the Apache License, Version 2.0 can be
+ found at /usr/share/common-licenses/Apache-2.0.
+
+License: BSD-3-Clause
+ All rights reserved.
+ .
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the University nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
+License: BSD-2-Clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ .
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ .
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: GPL-2+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the full text of the GNU General Public License version 2
+ can be found at /usr/share/common-licenses/GPL-2.
+
+License: Unicode-DFS-2015
+ Copyright © 1991-2015 Unicode, Inc. All rights reserved. Distributed under the
+ Terms of Use in http://www.unicode.org/copyright.html.
+ .
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of the Unicode data files and any associated documentation (the "Data Files")
+ or Unicode software and any associated documentation (the "Software") to deal
+ in the Data Files or Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, and/or sell copies
+ of the Data Files or Software, and to permit persons to whom the Data Files or
+ Software are furnished to do so, provided that
+ .
+ (a) this copyright and permission notice appear with all copies of the Data
+ Files or Software,
+ (b) this copyright and permission notice appear in associated documentation,
+ and
+ (c) there is clear notice in each modified Data File or in the Software as
+ well as in the documentation associated with the Data File(s) or Software
+ that the data or software has been modified.
+ .
+ THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD
+ PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN
+ THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
+ DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR
+ SOFTWARE.
+ .
+ Except as contained in this notice, the name of a copyright holder shall not
+ be used in advertising or otherwise to promote the sale, use or other dealings
+ in these Data Files or Software without prior written authorization of the
+ copyright holder.
--- /dev/null
+-----BEGIN DH PARAMETERS-----
+MIICCAKCAgEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxOb
+IlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjft
+awv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXT
+mmkWP6j9JM9fg2VdI9yjrZYcYvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhgh
+fDKQXkYuNs474553LBgOhgObJ4Oi7Aeij7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq
+5RXSJhiY+gUQFXKOWoqqxC2tMxcNBFB6M6hVIavfHLpk7PuFBFjb7wqK6nFXXQYM
+fbOXD4Wm4eTHq/WujNsJM9cejJTgSiVhnc7j0iYa0u5r8S/6BtmKCGTYdgJzPshq
+ZFIfKxgXeyAMu+EXV3phXWx3CYjAutlG4gjiT6B05asxQ9tb/OD9EI5LgtEgqSEI
+ARpyPBKnh+bXiHGaEL26WyaZwycYavTiPBqUaDS2FQvaJYPpyirUTOjbu8LbBN6O
++S6O/BQfvsqmKHxZR05rwF2ZspZPoJDDoiM7oYZRW+ftH2EpcM7i16+4G912IXBI
+HNAGkSfVsFqpk7TqmI2P3cGG/7fckKbAj030Nck0BjGZ//////////8CAQI=
+-----END DH PARAMETERS-----
--- /dev/null
+dovecot (1:2.4.1+dfsg1-1) unstable; urgency=medium
+
+ Dovecot 2.4 is a major upgrade from the previous 2.3 branch and introduces
+ an incompatible configuration language. Please see
+ https://doc.dovecot.org/2.4.0/installation/upgrade/2.3-to-2.4.html for
+ details on transitioning to the new language. The default configuration
+ shipped by Debian has been updated for compatibility with the new
+ configuration, but any locally maintained configuration will need to be
+ updated.
+
+ -- Noah Meyerhans <noahm@debian.org> Mon, 10 Mar 2025 13:28:20 -0400
+
+dovecot (1:2.3.2-1) unstable; urgency=medium
+
+ Upgrading to the 2.3 series may require manual configuration changes.
+ Some settings have been removed, while others have had their defaults
+ changed. Please see
+
+ /usr/share/doc/dovecot-core/wiki/Upgrading.2.3.txt.gz
+
+ or the online version at
+
+ https://wiki2.dovecot.org/Upgrading/2.3
+
+ for more information and review your configuration accordingly.
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Sat, 24 Mar 2018 00:34:07 +0200
+
+dovecot (1:2.2.31-1) unstable; urgency=medium
+
+ TLS is now enabled by default, using the ssl-cert-snakeoil certificate
+ provided by the ssl-cert package. Upgrades from older versions will be
+ prompted to accept the new configuration and enable TLS. If you have already
+ configured TLS yourself, you'll most probably want to keep your settings
+ intact.
+
+ See /usr/share/doc/dovecot-core/README.Debian for more information on the
+ certificate's default location and how to install your own certificates.
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Sun, 25 Jun 2017 01:09:28 +0300
+
+dovecot (1:2.2.21-1) unstable; urgency=medium
+
+ This release disables the dovecot.socket systemd unit by default. The unit is
+ disabled only if the dovecot.service unit is already enabled, making sure
+ that dovecot will start on system boot. If you are upgrading dovecot and
+ previously relied on dovecot.socket and dovecot.service being both enabled,
+ please re-enable dovecot.socket manually using
+
+ systemctl enable dovecot.socket
+
+ Future package updates will not disable the socket unit again. For details
+ regarding this decision, please see Debian bugs #803915 and #814999.
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org> Fri, 19 Feb 2016 16:54:27 +0200
+
+dovecot (1:2.1.7-7) unstable; urgency=high
+
+ If you are upgrading from stable or the earlier 2.1.7 packages in testing,
+ you are advised to backup your configuration files before continuing.
+ Carefully read /usr/share/doc/dovecot-core/README.Debian.gz for important
+ information about problems that might affect your upgrade.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Mon, 04 Feb 2013 16:27:17 -0500
+
+dovecot (1:2.0.5-1) experimental; urgency=low
+
+ * A lot of settings have changed. Dovecot v2.0 can still use most of
+ the v1.x configuration files, but it logs a lot of warnings at
+ startup. A quick and easy way to convert your old config file to
+ v2.0 format is:
+
+ doveconf -n -c dovecot-1.conf > dovecot-2.conf
+
+ This command logs a warning about each obsolete setting it
+ converts to the new format. You can either go through the warnings
+ to figure out what changes exactly were done, or you can simply
+ trust doveconf and replace your old config with the newly
+ generated one.
+
+ * Dovecot uses two system users for internal purposes now by
+ default: "dovenull" and "dovecot". "dovenull" user is used by
+ completely untrustworthy processes, while "dovecot" user is used
+ for slightly more trusted processes.
+
+ * In conf.d/10-mail.conf, the default mail_location is set to
+ mbox:~/mail:INBOX=/var/mail/%u
+ This has to be set because Dovecots auto-detection of mailboxes fails
+ when a user has no mail. I tried to provide a reasonable default
+ but advanced users will certainly want to change this.
+
+ Related to this, you might see an error in the logs about dovecot not
+ being able to chgrp certain files. This is because the default user
+ mailboxes in Debian belong to group mail which dovecot is not part of
+ (nor should be.) The Debian package has been patched so that the error
+ will not be fatal but for a more permanent solution, see here:
+ http://wiki2.dovecot.org/Errors/ChgrpNoPerm for some discussion. Or
+ you can set mail_location (and the equivalent settings in your MDA) so
+ that mailboxes are in the users home directory.
+
+ * No more convert plugin, use dsync instead
+
+ * No more expire-tool, use doveadm expunge instead. Also expire
+ configuration is different.
+
+ * Post-login scripts are configured differently and need to be modified
+
+ * Quota warnings are configured differently and the script may need
+ to be modified (most environment settings like $USER are gone)
+
+ * Global ACL filenames now require namespace prefix (e.g. if you use
+ "INBOX." prefix, /etc/acls/foo needs to be renamed to
+ /etc/acls/INBOX.foo
+
+ * Maildir: Permissions for newly created mail files are no longer
+ copied from dovecot-shared file, but instead from the mail
+ directory (e.g. for "foo" mailbox, they're taken from
+ ~/Maildir/.foo directory)
+
+ * dbox: v2.0 format is slightly different, but backwards
+ compatible. The main problem is that v2.0 no longer supports
+ maildir-dbox hybrid resulting from "fast Maildir migration". If
+ you have any Maildir files in your dbox, you need to convert them
+ somehow (some examples). You might also consider using dsync to
+ get rid of the old unused metadata in your dbox files.
+
+
+ * Pre-login and post-login CAPABILITY reply is now
+ different. Dovecot expects clients to recognize new automatically
+ sent capabilities. This should work with all commonly used
+ clients, but some rarely used clients might have problems. Either
+ get the client fixed, or set imap_capability manually.
+
+ * ManageSieve protocol was assigned an official port by IANA:
+ 4190. This is used by Pigeonhole by default now. If you want to
+ listen also on the old 2000 port, see the example in
+ /usr/share/doc/dovecot-common/wiki/ManageSieve.Configuration.txt.gz
+
+ * deliver binary was renamed to dovecot-lda (but a symlink still
+ exists for now)
+
+ * deliver -n parameter was replaced by lda_mailbox_autocreate
+ setting. The default also changed to "no".
+
+ * deliver -s parameter was replaced by lda_mailbox_autosubscribe
+ setting. The default is "no", as before.
+
+ -- Marco Nenciarini <mnencia@debian.org> Mon, 18 Oct 2010 12:59:14 +0200
+
+dovecot (1:1.0.12-1) unstable; urgency=low
+
+ * mail_extra_groups=mail setting is often used insecurely to give Dovecot
+ access to create dotlocks to /var/mail directory. The setting has been
+ replaced by mail_privileged_group and mail_access_groups.
+ Read also: http://dovecot.org/list/dovecot/2008-March/029196.html
+
+ -- Fabio Tranchitella <kobold@debian.org> Thu, 06 Mar 2008 15:46:03 +0100
+
+dovecot (1.0.beta3-1) unstable; urgency=low
+
+ * Starting from this release, dovecot-lda is included in dovecot-common.
+ The previous executable deliver has been removed from the upstream
+ source package.
+ * Other new features recently added include
+ + vpopmail support
+ + quota support
+ + GSSAPI support
+ * All these new features mean there are some configuration file changes
+ please review the default /etc/dovecot/dovecot.conf and merge in any
+ new bits. (If you don't use any new features, your configuration should
+ remain compatible.)
+
+ -- Fabio Tranchitella <kobold@debian.org> Fri, 17 Feb 2006 17:05:37 +0000
+
+dovecot (1.0.alpha4-1) unstable; urgency=low
+
+ * This is the 1.0alpha branch. Once again there have been incompatible
+ changes to the syntax of /etc/dovecot/dovecot.conf.
+ * The dovecot-common package now includes the zlib plugin for compressed
+ mboxen. Make sure you have mail_use_modules=yes in imap and/or pop3
+ section in /etc/dovecot/dovecot.conf if you want to use this.
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 20 Jul 2005 06:30:37 -0400
+
+dovecot (0.99.20050712-1) unstable; urgency=low
+
+ * This is the 1.0stable development branch. There have been major
+ changes and new features have been added so check your configuration
+ carefully. In particular, /etc/dovecot/dovecot-mysql.conf and
+ /etc/dovecot/dovecot-pgsql.conf have been replaced by
+ /etc/dovecot/dovecot-sql.conf .
+
+ -- Jaldhar H. Vyas <jaldhar@debian.org> Wed, 20 Jul 2005 06:30:37 -0400
--- /dev/null
+Dovecot Debian package
+======================
+
+Please refer to http://wiki2.dovecot.org for the full documentation of this
+package and its configuration.
+
+Note that the default value for mbox_write_locks differs between Debian and
+upstream Dovecot:
+ Debian: mbox_write_locks = "fnctl dotlock"
+ Dovecot: mbox_write_locks = "dotlock fnctl"
+
+TLS setup
+---------
+
+TLS is enabled by default on new installations. Dovecot will use the
+self-signed certificate provided by ssl-cert-snakeoil by default. The
+certificate and key are accessed via symbolic links in /etc/dovecot/private.
+
+If you wish to use your own certificates, feel free to replace the symbolic
+links in /etc/dovecot/private with the actual certificate/key of your
+preference, or place the certificate and key at the location of your preference
+and update /etc/dovecot/conf.d/10-ssl.conf accordingly.
+
+dovenull group
+--------------
+
+Upstream recommends that for security's sake, the dovenull user should belong
+to its own private group to which no one else belongs. Prior to 2.2.9-1, the
+dovenull user was in the nogroup group which contains other users. This has
+been rectified for new installs but upgraders should make the change
+themselves like this:
+
+# addgroup --system --group dovenull
+# usermod -g dovenull dovenull
+
--- /dev/null
+package-status: dovecot-common dovecot-core dovecot-dev dovecot-gssapi dovecot-imapd dovecot-ldap dovecot-lmtpd dovecot-managesieved dovecot-mysql dovecot-pgsql dovecot-pop3d dovecot-sieve dovecot-sqlite
--- /dev/null
+#!/bin/bash
+
+set -e
+
+pecho() {
+ echo "$@"
+ echo "$@"|sed 's/./-/g'
+}
+
+
+if [ -x /usr/bin/doveconf ]
+then
+ echo
+ cat <<-EOF
+ You can append "doveconf -n" output to the bug report. It will help the
+ maintainers to better understand your configuration.
+ It normally doesn't contain any sensitive information, but you should
+ review it before sending.
+ EOF
+ echo
+ yesno "Do you want to append \"doveconf -n\" output to your bug report? " yep
+ if [ "$REPLY" == yep ]
+ then
+ echo >&3
+ pecho "dovecot configuration" >&3
+ /usr/bin/doveconf -n >&3
+ fi
+fi
--- /dev/null
+etc/dovecot/conf.d
+etc/dovecot/private
+usr/share/dovecot/protocols.d
+var/lib/dovecot
--- /dev/null
+NEWS
+TODO
--- /dev/null
+# /etc/default/dovecot
+
+# Set to '1' to allow Dovecot daemons to produce core dumps
+#ALLOW_COREDUMPS=1
--- /dev/null
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: dovecot
+# Required-Start: $local_fs $remote_fs $network $syslog $time
+# Required-Stop: $local_fs $remote_fs $network $syslog
+# Should-Start: postgresql mysql slapd winbind nslcd
+# Should-Stop: postgresql mysql slapd winbind nslcd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Dovecot init script
+# Description: Init script for dovecot services
+### END INIT INFO
+
+# Author: Miquel van Smoorenburg <miquels@cistron.nl>.
+# Modified for Debian GNU/Linux
+# by Ian Murdock <imurdock@gnu.ai.mit.edu>.
+#
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DESC="IMAP/POP3 mail server"
+NAME=dovecot
+DAEMON=/usr/sbin/dovecot
+DAEMON_ARGS=""
+SCRIPTNAME=/etc/init.d/$NAME
+CONF=/etc/dovecot/${NAME}.conf
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Exit if the configuration file doesn't exist
+[ -f "$CONF" ] || exit 0
+
+# Exit if explicitly told to
+[ "$ENABLED" != "0" ] || exit 0
+
+# Allow core dumps if requested
+[ "$ALLOW_COREDUMPS" != "1" ] || ulimit -c unlimited
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+# conf file readable?
+if [ ! -r ${CONF} ]; then
+ log_daemon_msg "${CONF}: not readable" "$NAME" && log_end_msg 1;
+ exit 1;
+fi
+
+# dont check for inetd.conf if its not installed
+if [ -f /etc/inetd.conf ]; then
+ # The init script should do nothing if dovecot or another imap/pop3 server
+ # is being run from inetd, and dovecot is configured to run as an imap or
+ # pop3 service
+ for p in `sed -r "s/^ *(([^:]+|\[[^]]+]|\*):)?(pop3s?|imaps?)[ \t].*/\3/;t;d" \
+ /etc/inetd.conf`
+ do
+ for q in `doveconf -n -h protocols`
+ do
+ if [ $p = $q ]; then
+ log_daemon_msg "protocol ${p} configured both in inetd and in dovecot" "$NAME" && log_end_msg 1
+ exit 0
+ fi
+ done
+ done
+fi
+
+# determine the location of the PID file
+# override by setting base_dir in conf file or PIDBASE in /etc/defaults/$NAME
+PIDBASE=${PIDBASE:-`doveconf -n -c ${CONF} -h base_dir`}
+PIDFILE=${PIDBASE:-/var/run/dovecot}/master.pid
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test -- -c ${CONF} > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -c ${CONF} \
+ $DAEMON_ARGS \
+ || return 2
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name ${DAEMON##*/}
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE --name ${DAEMON##*/}
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal HUP --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+
+case "$1" in
+ start)
+ log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) log_end_msg 0 ;;
+ 2) log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) log_end_msg 0 ;;
+ 2) log_end_msg 1 ;;
+ esac
+ ;;
+ reload|force-reload)
+ log_daemon_msg "Reloading $DESC" "$NAME"
+ do_reload
+ log_end_msg $?
+ ;;
+ restart)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ status)
+ status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $?
+ ;;
+ *)
+ echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload|status}" >&2
+ exit 3
+ ;;
+esac
--- /dev/null
+#%PAM-1.0
+
+@include common-auth
+@include common-account
+@include common-session
+
--- /dev/null
+debian/dh.pem usr/share/dovecot
+debian/dovecot-openssl.cnf usr/share/dovecot
+debian/maildirmake.dovecot usr/bin
--- /dev/null
+# hardening flags are set and blhc succeeds; might need some investigation
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/config]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/gdbhelper]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/libdovecot-storage.so.0.0.0]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/modules/lib20_fts_plugin.so]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/script-login]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/script]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/xml2text]
+dovecot-core: package-contains-empty-directory [usr/share/dovecot/protocols.d/]
+dovecot-core: package-contains-empty-directory [usr/lib/dovecot/modules/dict/]
+dovecot-core: spelling-error-in-readme-debian dovenull dovenull (duplicate word) dovenull [usr/share/doc/dovecot-core/README.Debian]
+dovecot-core: library-not-linked-against-libc [usr/lib/dovecot/modules/old-stats/libold_stats_mail.so]
+dovecot-core: library-not-linked-against-libc [usr/lib/dovecot/modules/old-stats/libstats_auth.so]
+dovecot-core [armel]: library-not-linked-against-libc usr/lib/dovecot/modules/lib20_listescape_plugin.so
+# ignore internal libraries without dependency information
+dovecot-core: shared-library-lacks-prerequisites [usr/lib/dovecot/modules/lib20_listescape_plugin.so]
+dovecot-core: shared-library-lacks-prerequisites [usr/lib/dovecot/modules/settings/libpigeonhole_settings.so]
+# ignore extra man pages
+dovecot-core: spare-manual-page [usr/share/man/man1/deliver.1.gz]
+# keep all man pages in section 1 for now
+dovecot-core: manual-page-for-system-command [usr/sbin/dovecot]
+# ignore executable-in-usr-lib, maybe move to libexec at some point
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/anvil]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/auth]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/config]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/dict]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/dns-client]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/doveadm-server]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/dovecot-lda]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/gdbhelper]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/health-check.sh]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/indexer]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/indexer-worker]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/log]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/quota-status]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/rawlog]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/script]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/script-login]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/stats]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/xml2text]
+# ignore equal stub files
+dovecot-core: duplicate-files usr/share/doc/dovecot-core/wiki/Pigeonhole.ManageSieve.Install.txt usr/share/doc/dovecot-core/wiki/Plugins.Stats.txt
+# ignore stopwords txt files not in /usr/share/doc
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_da.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_de.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_en.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_es.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_fi.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_fr.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_it.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_nl.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_no.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_pt.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_ro.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_ru.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_sv.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_tr.txt]
+# ulimit -c might be specified but is supported by bash and dash
+dovecot-core: bash-term-in-posix-shell '| ulimit' [etc/init.d/dovecot:43]
--- /dev/null
+debian/maildirmake.dovecot.1
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ "$1" = "configure" ]; then
+ CONFFILES="dovecot.conf \
+ conf.d/10-auth.conf \
+ conf.d/10-logging.conf \
+ conf.d/10-mail.conf \
+ conf.d/10-master.conf \
+ conf.d/10-metrics.conf \
+ conf.d/10-ssl.conf \
+ conf.d/15-lda.conf \
+ conf.d/15-mailboxes.conf \
+ conf.d/30-dict-server.conf \
+ conf.d/90-acl.conf \
+ conf.d/90-fts.conf \
+ conf.d/90-quota.conf \
+ conf.d/auth-deny.conf.ext \
+ conf.d/auth-master.conf.ext \
+ conf.d/auth-oauth2.conf.ext \
+ conf.d/auth-passwdfile.conf.ext \
+ conf.d/auth-sql.conf.ext \
+ conf.d/auth-static.conf.ext \
+ conf.d/auth-system.conf.ext"
+
+ OLD_CONFFILES="conf.d/auth-vpopmail.conf.ext \
+ dovecot-dict-auth.conf.ext \
+ dovecot-dict-sql.conf.ext \
+ dovecot-sql.conf.ext \
+ conf.d/10-director.conf \
+ conf.d/10-tcpwrapper.conf \
+ conf.d/90-plugin.conf"
+
+ ## Users
+ #
+
+ adduser --system --group --home /usr/lib/dovecot --gecos "Dovecot mail server" \
+ --no-create-home --disabled-password --quiet dovecot || true
+
+ adduser --system --group --home /nonexistent --no-create-home --gecos "Dovecot login user" \
+ --disabled-password --quiet dovenull || true
+
+ for conffile in $CONFFILES ; do
+ # Tell ucf that the file in /usr/share/dovecot is the latest
+ # maintainer version, and let it handle how to manage the real
+ # configuration file in /etc/dovecot.
+ ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+ ucfr dovecot-core "/etc/dovecot/$conffile"
+ if [ "$conffile" != "dovecot.conf" ] && [ -f "/etc/dovecot/$conffile" ] &&
+ [ "$(echo "$conffile" | cut -b -7)" != "conf.d/" ]; then
+ chmod 0640 "/etc/dovecot/$conffile"
+ chgrp dovecot "/etc/dovecot/$conffile"
+ fi
+ done
+
+ for oldconffile in $OLD_CONFFILES ; do
+ if [ -e "/etc/dovecot/$oldconffile" ]; then
+ modified=$(ucfq -w /etc/dovecot/$oldconffile | cut -d: -f4)
+ if [ "$modified" = "No" ]; then
+ echo "Removing unmodified obsolete configuration file '/etc/dovecot/$oldconffile'"
+ rm -f "/etc/dovecot/$oldconffile"
+ else
+ echo "Locally modified configuration file '/etc/dovecot/$oldconffile' is obsolete. Please remove."
+ fi
+ ucf --purge "/etc/dovecot/$oldconffile"
+ ucfr --purge dovecot-core "/etc/dovecot/$oldconffile"
+ fi
+ done
+
+
+ if ! dpkg-statoverride --list /etc/dovecot/private >/dev/null; then
+ dpkg-statoverride --quiet --update --add root root 0700 /etc/dovecot/private
+ fi
+
+ # SSL configuration
+ # Use the ssl-cert-snakeoil certificate in the following cases:
+ # - On new installations
+ if [ -z "$2" ]; then
+ if [ ! -e /etc/dovecot/private/dovecot.key ] && \
+ [ ! -e /etc/dovecot/private/dovecot.pem ] && \
+ [ -e /etc/ssl/certs/ssl-cert-snakeoil.pem ] && \
+ [ -e /etc/ssl/private/ssl-cert-snakeoil.key ]; then
+ ln -s /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/dovecot/private/dovecot.pem
+ ln -s /etc/ssl/private/ssl-cert-snakeoil.key /etc/dovecot/private/dovecot.key
+ fi
+ fi
+fi
+
+if [ "$1" = "triggered" ]; then
+ if [ -x "/etc/init.d/dovecot" ]; then
+ invoke-rc.d dovecot restart
+ fi
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ "$1" = "purge" ] ; then
+ for conffile in $(ucfq --with-colons dovecot-core | cut -d: -f1); do
+ # we mimic dpkg as closely as possible, so we remove configuration
+ # files with dpkg backup extensions too:
+ ### Some of the following is from Tore Anderson:
+ for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist; do
+ rm -f "$conffile$ext"
+ done
+ # remove the configuration file itself
+ rm -f "$conffile"
+ # and finally clear it out from the ucf database
+ if which ucf >/dev/null; then
+ ucf --purge "$conffile"
+ fi
+ if which ucfr >/dev/null; then
+ ucfr --purge dovecot-core "$conffile"
+ fi
+ done
+
+ userdel dovecot || true;
+ userdel dovenull || true;
+
+ if [ -d /run/dovecot ]; then
+ rm -rf /run/dovecot
+ fi
+
+ # Remove dovecot.pem and dovecot.key only if they are symlinks; otherwise
+ # we might remove CA-issued certificates that are difficult and/or
+ # expensive to replace.
+ for file in /etc/dovecot/private/dovecot.pem /etc/dovecot/private/dovecot.key; do
+ if [ -h "$file" ]; then
+ rm -f "$file"
+ else
+ echo "${file} is not a symbolic link, skipping removal." >&2
+ fi
+ done
+fi
+
+if [ "$1" = "triggered" ]; then
+ if [ -x "/etc/init.d/dovecot" ]; then
+ invoke-rc.d dovecot start
+ fi
+fi
+
+#DEBHELPER#
--- /dev/null
+interest-noawait register-dovecot-plugin
+interest-noawait /usr/lib/dovecot/modules
--- /dev/null
+/usr/share/dovecot/dovecot-abi contains that portion of DOVECOT_ABI_VERSION
+from configure.ac outside parentheses (i.e not including the package version.)
+It might look something like this:
+
+2.2.ABIv10
+
+...packages providing plugins for dovecot can use this information in their
+dependencies to avoid having to update every time there is a minor update to
+the dovecot package.
+
--- /dev/null
+# plugins probably need the package name
+dovecot-dev: package-name-defined-in-config-h usr/include/dovecot/config.h
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-flatcurve
--- /dev/null
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "configure" ]; then
+ CONFFILES="conf.d/90-fts-flatcurve.conf"
+
+ for conffile in $CONFFILES ; do
+ # Tell ucf that the file in /usr/share/dovecot is the latest
+ # maintainer version, and let it handle how to manage the real
+ # configuration file in /etc/dovecot.
+ ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+ ucfr dovecot-ldap "/etc/dovecot/$conffile"
+ if [ "$conffile" != "dovecot.conf" ] && [ -f "/etc/dovecot/$conffile" ] &&
+ [ "$(echo "$conffile" | cut -b -7)" != "conf.d/" ]; then
+ chmod 0600 "/etc/dovecot/$conffile"
+ fi
+ done
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ "$1" = "purge" ] ; then
+ for conffile in $(ucfq --with-colons dovecot-flatcurve | cut -d: -f1); do
+ # we mimic dpkg as closely as possible, so we remove configuration
+ # files with dpkg backup extensions too:
+ ### Some of the following is from Tore Anderson:
+ for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist; do
+ rm -f "$conffile$ext"
+ done
+ # remove the configuration file itself
+ rm -f "$conffile"
+ # and finally clear it out from the ucf database
+ if which ucf >/dev/null; then
+ ucf --purge "$conffile"
+ fi
+ if which ucfr >/dev/null; then
+ ucfr --purge dovecot-flatcurve "$conffile"
+ fi
+ done
+
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ -d /run/systemd/system ]; then
+ deb-systemd-invoke stop dovecot.socket || true
+fi
+
+if [ -x "/etc/init.d/dovecot" ]; then
+ invoke-rc.d dovecot stop
+fi
+
+#DEBHELPER#
--- /dev/null
+activate-noawait register-dovecot-plugin
+
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-gssapi
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-imapd
--- /dev/null
+# hardening flags are set and blhc succeeds; might need some investigation
+dovecot-imapd: hardening-no-fortify-functions [usr/lib/dovecot/imap]
--- /dev/null
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "configure" ]; then
+ CONFFILES="conf.d/20-imap.conf"
+
+ for conffile in $CONFFILES ; do
+ # Tell ucf that the file in /usr/share/dovecot is the latest
+ # maintainer version, and let it handle how to manage the real
+ # configuration file in /etc/dovecot.
+ ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+ ucfr dovecot-imapd "/etc/dovecot/$conffile"
+ done
+
+ cat <<EOF > /usr/share/dovecot/protocols.d/imapd.protocol
+protocols {
+ imap = yes
+}
+EOF
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ "$1" = "purge" ] ; then
+ for conffile in $(ucfq --with-colons dovecot-imapd | cut -d: -f1); do
+ # we mimic dpkg as closely as possible, so we remove configuration
+ # files with dpkg backup extensions too:
+ ### Some of the following is from Tore Anderson:
+ for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist; do
+ rm -f "$conffile$ext"
+ done
+ # remove the configuration file itself
+ rm -f "$conffile"
+ # and finally clear it out from the ucf database
+ if which ucf >/dev/null; then
+ ucf --purge "$conffile"
+ fi
+ if which ucfr >/dev/null; then
+ ucfr --purge dovecot-imapd "$conffile"
+ fi
+ done
+
+fi
+
+if [ "$1" = "purge" ] || [ "$1" = "remove" ]; then
+ rm -f /usr/share/dovecot/protocols.d/imapd.protocol
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ -d /run/systemd/system ]; then
+ deb-systemd-invoke stop dovecot.socket || true
+fi
+
+if [ -x "/etc/init.d/dovecot" ]; then
+ invoke-rc.d dovecot stop
+fi
+
+#DEBHELPER#
--- /dev/null
+activate-noawait register-dovecot-plugin
--- /dev/null
+[Dovecot IMAP]
+title=Secure mail server (IMAP)
+description=Dovecot is a mail server whose major goals are security and extreme
+ reliability.
+ports=143/tcp
+
+[Dovecot Secure IMAP]
+title=Secure mail server (IMAPS)
+description=Dovecot is a mail server whose major goals are security and extreme
+ reliability.
+ports=993/tcp
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-ldap
--- /dev/null
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "configure" ]; then
+ CONFFILES="conf.d/auth-ldap.conf.ext"
+
+ for conffile in $CONFFILES ; do
+ # Tell ucf that the file in /usr/share/dovecot is the latest
+ # maintainer version, and let it handle how to manage the real
+ # configuration file in /etc/dovecot.
+ ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+ ucfr dovecot-ldap "/etc/dovecot/$conffile"
+ if [ "$conffile" != "dovecot.conf" ] && [ -f "/etc/dovecot/$conffile" ] &&
+ [ "$(echo "$conffile" | cut -b -7)" != "conf.d/" ]; then
+ chmod 0600 "/etc/dovecot/$conffile"
+ fi
+ done
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ "$1" = "purge" ] ; then
+ CONFFILES="/etc/dovecot/dovecot-ldap.conf.ext \
+ /etc/dovecot/conf.d/auth-ldap.conf.ext"
+
+ for conffile in $CONFFILES; do
+ # we mimic dpkg as closely as possible, so we remove configuration
+ # files with dpkg backup extensions too:
+ ### Some of the following is from Tore Anderson:
+ for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist; do
+ rm -f "$conffile$ext"
+ done
+ # remove the configuration file itself
+ rm -f "$conffile"
+ # and finally clear it out from the ucf database
+ if which ucf >/dev/null; then
+ ucf --purge "$conffile"
+ fi
+ if which ucfr >/dev/null; then
+ ucfr --purge dovecot-ldap "$conffile"
+ fi
+ done
+fi
+
+#DEBHELPER#
--- /dev/null
+activate-noawait register-dovecot-plugin
+
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-lmtpd
--- /dev/null
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "configure" ]; then
+ CONFFILES="conf.d/20-lmtp.conf"
+
+ for conffile in $CONFFILES ; do
+ # Tell ucf that the file in /usr/share/dovecot is the latest
+ # maintainer version, and let it handle how to manage the real
+ # configuration file in /etc/dovecot.
+ ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+ ucfr dovecot-lmtpd "/etc/dovecot/$conffile"
+ done
+
+ cat <<EOF > /usr/share/dovecot/protocols.d/lmtpd.protocol
+protocols {
+ lmtp = yes
+}
+EOF
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ "$1" = "purge" ] ; then
+ for conffile in $(ucfq --with-colons dovecot-lmtpd | cut -d: -f1); do
+ # we mimic dpkg as closely as possible, so we remove configuration
+ # files with dpkg backup extensions too:
+ ### Some of the following is from Tore Anderson:
+ for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist; do
+ rm -f "$conffile$ext"
+ done
+ # remove the configuration file itself
+ rm -f "$conffile"
+ # and finally clear it out from the ucf database
+ if which ucf >/dev/null; then
+ ucf --purge "$conffile"
+ fi
+ if which ucfr >/dev/null; then
+ ucfr --purge dovecot-lmtpd "$conffile"
+ fi
+ done
+
+fi
+
+if [ "$1" = "purge" ] || [ "$1" = "remove" ]; then
+ rm -f /usr/share/dovecot/protocols.d/lmtpd.protocol
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ -d /run/systemd/system ]; then
+ deb-systemd-invoke stop dovecot.socket || true
+fi
+
+if [ -x "/etc/init.d/dovecot" ]; then
+ invoke-rc.d dovecot stop
+fi
+
+#DEBHELPER#
--- /dev/null
+activate-noawait register-dovecot-plugin
+
--- /dev/null
+Removing dovecot-managesieved
+-----------------------------
+
+If you remove (i.e. with dpkg -r or aptitude remove) this package you will
+get an error something like this:
+
+doveconf: Fatal: Error in configuration file /etc/dovecot/dovecot.conf:
+service(managesieve-login): executable is empty
+
+To avoid it, make sure
+/etc/dovecot/conf.d/etc/dovecot/conf.d/20-managesieve.conf has been deleted,
+moved or completely commented out first. Alternatively, purge the package
+instead of removing it.
+
+If you do not do this, dovecot-core will be left in an unconfigured state
+because the error will cause a trigger to fail. To fix this simply reinstall
+dovecot-core.
--- /dev/null
+pigeonhole/doc/example-config/conf.d/??-managesieve* usr/share/dovecot/conf.d
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-managesieved
--- /dev/null
+dovecot-managesieved: shared-library-lacks-prerequisites [usr/lib/dovecot/modules/settings/libmanagesieve_settings.so]
+dovecot-managesieved: library-not-linked-against-libc [usr/lib/dovecot/modules/settings/libmanagesieve_settings.so]
--- /dev/null
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "configure" ]; then
+ CONFFILES="conf.d/20-managesieve.conf"
+
+ for conffile in $CONFFILES ; do
+ # Tell ucf that the file in /usr/share/dovecot is the latest
+ # maintainer version, and let it handle how to manage the real
+ # configuration file in /etc/dovecot.
+ ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+ ucfr dovecot-managesieved "/etc/dovecot/$conffile"
+ done
+
+ cat <<EOF > /usr/share/dovecot/protocols.d/managesieved.protocol
+protocols {
+ sieve = yes
+}
+EOF
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ "$1" = "purge" ] ; then
+ for conffile in $(ucfq --with-colons dovecot-managesieved | cut -d: -f1); do
+ # we mimic dpkg as closely as possible, so we remove configuration
+ # files with dpkg backup extensions too:
+ ### Some of the following is from Tore Anderson:
+ for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist; do
+ rm -f "$conffile$ext"
+ done
+ # remove the configuration file itself
+ rm -f "$conffile"
+ # and finally clear it out from the ucf database
+ if which ucf >/dev/null; then
+ ucf --purge "$conffile"
+ fi
+ if which ucfr >/dev/null; then
+ ucfr --purge dovecot-managesieved "$conffile"
+ fi
+ done
+
+fi
+
+if [ "$1" = "purge" ] || [ "$1" = "remove" ]; then
+ rm -f /usr/share/dovecot/protocols.d/managesieved.protocol
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ -d /run/systemd/system ]; then
+ deb-systemd-invoke stop dovecot.socket || true
+fi
+
+if [ -x "/etc/init.d/dovecot" ]; then
+ invoke-rc.d dovecot stop
+fi
+
+#DEBHELPER#
--- /dev/null
+activate-noawait register-dovecot-plugin
+
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-mysql
--- /dev/null
+activate-noawait register-dovecot-plugin
+
--- /dev/null
+#
+# SSLeay configuration file for Dovecot.
+#
+
+RANDFILE = /dev/urandom
+
+[ req ]
+default_bits = 2048
+default_keyfile = privkey.pem
+distinguished_name = req_distinguished_name
+prompt = no
+policy = policy_anything
+req_extensions = v3_req
+x509_extensions = v3_req
+
+[ req_distinguished_name ]
+organizationName = Dovecot mail server
+organizationalUnitName = @commonName@
+commonName = @commonName@
+emailAddress = @emailAddress@
+
+[ v3_req ]
+basicConstraints = CA:FALSE
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-pgsql
--- /dev/null
+activate-noawait register-dovecot-plugin
+
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-pop3d
--- /dev/null
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "configure" ]; then
+ CONFFILES="conf.d/20-pop3.conf"
+
+ for conffile in $CONFFILES ; do
+ # Tell ucf that the file in /usr/share/dovecot is the latest
+ # maintainer version, and let it handle how to manage the real
+ # configuration file in /etc/dovecot.
+ ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+ ucfr dovecot-pop3d "/etc/dovecot/$conffile"
+ done
+
+ cat <<EOF > /usr/share/dovecot/protocols.d/pop3d.protocol
+protocols {
+ pop3 = yes
+}
+EOF
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ "$1" = "purge" ] ; then
+ for conffile in $(ucfq --with-colons dovecot-pop3d | cut -d: -f1); do
+ # we mimic dpkg as closely as possible, so we remove configuration
+ # files with dpkg backup extensions too:
+ ### Some of the following is from Tore Anderson:
+ for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist; do
+ rm -f "$conffile$ext"
+ done
+ # remove the configuration file itself
+ rm -f "$conffile"
+ # and finally clear it out from the ucf database
+ if which ucf >/dev/null; then
+ ucf --purge "$conffile"
+ fi
+ if which ucfr >/dev/null; then
+ ucfr --purge dovecot-pop3d "$conffile"
+ fi
+ done
+
+fi
+
+if [ "$1" = "purge" ] || [ "$1" = "remove" ]; then
+ rm -f /usr/share/dovecot/protocols.d/pop3d.protocol
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ -d /run/systemd/system ]; then
+ deb-systemd-invoke stop dovecot.socket || true
+fi
+
+if [ -x "/etc/init.d/dovecot" ]; then
+ invoke-rc.d dovecot stop
+fi
+
+#DEBHELPER#
--- /dev/null
+activate-noawait register-dovecot-plugin
+
--- /dev/null
+[Dovecot POP3]
+title=Secure mail server (POP3)
+description=Dovecot is a mail server whose major goals are security and extreme
+ reliability.
+ports=110/tcp
+
+[Dovecot Secure POP3]
+title=Secure mail server (POP3S)
+description=Dovecot is a mail server whose major goals are security and extreme
+ reliability.
+ports=995/tcp
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-sieve
--- /dev/null
+# ignore internal library without dependency information
+dovecot-sieve: shared-library-lacks-prerequisites [usr/lib/dovecot/modules/sieve/lib90_sieve_imapsieve_plugin.so]
+# alias for sieve-dump
+dovecot-sieve: spare-manual-page [usr/share/man/man1/sieved.1.gz]
--- /dev/null
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "configure" ]; then
+ CONFFILES="conf.d/90-sieve.conf \
+ conf.d/90-sieve-extprograms.conf"
+
+ for conffile in $CONFFILES ; do
+ # Tell ucf that the file in /usr/share/dovecot is the latest
+ # maintainer version, and let it handle how to manage the real
+ # configuration file in /etc/dovecot.
+ ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+ ucfr dovecot-sieve "/etc/dovecot/$conffile"
+ done
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ "$1" = "purge" ] ; then
+ for conffile in $(ucfq --with-colons dovecot-sieve | cut -d: -f1); do
+ # we mimic dpkg as closely as possible, so we remove configuration
+ # files with dpkg backup extensions too:
+ ### Some of the following is from Tore Anderson:
+ for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist; do
+ rm -f "$conffile$ext"
+ done
+ # remove the configuration file itself
+ rm -f "$conffile"
+ # and finally clear it out from the ucf database
+ if which ucf >/dev/null; then
+ ucf --purge "$conffile"
+ fi
+ if which ucfr >/dev/null; then
+ ucfr --purge dovecot-sieve "$conffile"
+ fi
+ done
+fi
+
+#DEBHELPER#
--- /dev/null
+activate-noawait register-dovecot-plugin
+
--- /dev/null
+After installing this package, the solr XML schema can be found in
+/usr/share/dovecot/solr-schema-*.xml
--- /dev/null
+doc/solr-*.xml usr/share/dovecot
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-solr
--- /dev/null
+activate-noawait register-dovecot-plugin
+
--- /dev/null
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-sqlite
--- /dev/null
+activate-noawait register-dovecot-plugin
+
--- /dev/null
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "configure" ]; then
+ CONFFILES="conf.d/20-submission.conf"
+
+ for conffile in $CONFFILES ; do
+ # Tell ucf that the file in /usr/share/dovecot is the latest
+ # maintainer version, and let it handle how to manage the real
+ # configuration file in /etc/dovecot.
+ ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+ # Use --force to allow hijacking the file from dovecot-submission
+ ucfr --force dovecot-submissiond "/etc/dovecot/$conffile"
+ done
+
+ if [ -f /usr/share/dovecot/protocols.d/submission.protocol ]; then
+ rm -f /usr/share/dovecot/protocols.d/submission.protocol
+ fi
+ cat <<EOF > /usr/share/dovecot/protocols.d/submissiond.protocol
+protocols {
+ submission = yes
+}
+EOF
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ "$1" = "purge" ] ; then
+ for conffile in $(ucfq --with-colons dovecot-submissiond | cut -d: -f1); do
+ # we mimic dpkg as closely as possible, so we remove configuration
+ # files with dpkg backup extensions too:
+ ### Some of the following is from Tore Anderson:
+ for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist; do
+ rm -f "$conffile$ext"
+ done
+ # remove the configuration file itself
+ rm -f "$conffile"
+ # and finally clear it out from the ucf database
+ if which ucf >/dev/null; then
+ ucf --purge "$conffile"
+ fi
+ if which ucfr >/dev/null; then
+ ucfr --purge dovecot-submissiond "$conffile"
+ fi
+ done
+
+fi
+
+if [ "$1" = "purge" ] || [ "$1" = "remove" ]; then
+ rm -f /usr/share/dovecot/protocols.d/submissiond.protocol
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/sh
+set -e
+
+if [ -d /run/systemd/system ]; then
+ deb-systemd-invoke stop dovecot.socket || true
+fi
+
+if [ -x "/etc/init.d/dovecot" ]; then
+ invoke-rc.d dovecot stop
+fi
+
+#DEBHELPER#
--- /dev/null
+activate-noawait register-dovecot-plugin
--- /dev/null
+[DEFAULT]
+pristine-tar = True
+sign-tags = True
+component=pigeonhole
+
+[buildpackage]
+export-dir = ../build-area/
+
+[dch]
+meta = True
+id-length = 7
+
+[pq]
+patch-numbers = False
--- /dev/null
+#!/bin/sh
+#
+# maildirmake.dovecot -- create maildirs
+# Copyright (c) 2003, Jaldhar H. Vyas
+# "Do what thou wilt" shall be the whole of the license.
+#
+dir="$1"
+owner="$2"
+if [ -z "$dir" ]; then
+ echo "Must supply a directory path"
+ exit 1
+fi
+
+if [ "$dir" = "-h" ]; then
+ echo "usage: $0 directory [user]"
+ exit 0
+fi
+
+umask 077
+mkdir -p "$dir/cur" "$dir/new" "$dir/tmp" || exit 1
+chmod 0700 "$dir" "$dir/cur" "$dir/new" "$dir/tmp" || exit 1
+
+if [ -n "$owner" ]; then
+ chown -R "$owner" "$dir" || exit 1
+fi
+
+exit 0
+
--- /dev/null
+.\" Hey, EMACS: -*- nroff -*-
+.\" First parameter, NAME, should be all caps
+.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
+.\" other parameters are allowed: see man(7), man(1)
+.TH "MAILDIRMAKE.DOVECOT" "1" "23 November 2005"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh disable hyphenation
+.\" .hy enable hyphenation
+.\" .ad l left justify
+.\" .ad b justify to both left and right margins
+.\" .nf disable filling
+.\" .fi enable filling
+.\" .br insert line break
+.\" .sp <n> insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+maildirmake.dovecot \- creates maildirs and maildir subfolders
+.SH SYNOPSIS
+.B maildirmake.dovecot
+\fBdirectory\fR [ \fRowner\fR ] \fR
+.br
+.SH DESCRIPTION
+.\" TeX users may be more comfortable with the \fB<whatever>\fP and
+.\" \fI<whatever>\fP escape sequences to invode bold face and italics,
+.\" respectively.
+\fBmaildirmake.dovecot\fP creates a maildir and its maildir subfolders (cur, new, tmp).
+\fBdirectory\fP is the name of the new maildir. If \fBdirectory\fP exists cur, new, tmp directories are created inside it.
+.TP
+You can specify \fBowner\fP to change directories ownership if you have ownership modification permission.
+.TP
+\fBmaildirmake.dovecot\fP is very basic. It is strongly recommended to use \fBmaildirmake\fP if you want to do serious maildir management.
+.SH OPTIONS
+.TP
+\fB-h\fP
+Show options summary and exits immediately.
+.SH SEE ALSO
+.BR maildirmake (1).
+.BR maildir (5).
+.SH AUTHOR
+.TP
+dovecot was written by Timo Sirainen <tss@iki.fi>. maildirmake.dovecot script was written by Jaldhar H. Vyas
+.PP
+This manual page was written by Henry Precheur <henry@precheur.org>,
+for the Debian project (but may be used by others).
--- /dev/null
+From 4c8919f9470ee61efe318fa4f7d410ba12098eed Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Wed, 3 Jun 2026 12:56:13 +0000
+Subject: [PATCH 1/4] auth: db-oauth2: Reduce nesting in token_in_scope via
+ early return
+
+---
+ src/auth/db-oauth2.c | 48 ++++++++++++++++++++++----------------------
+ 1 file changed, 24 insertions(+), 24 deletions(-)
+
+Index: trixie/src/auth/db-oauth2.c
+===================================================================
+--- trixie.orig/src/auth/db-oauth2.c
++++ trixie/src/auth/db-oauth2.c
+@@ -553,32 +553,32 @@ static bool
+ db_oauth2_token_in_scope(struct db_oauth2_request *req,
+ enum passdb_result *result_r, const char **error_r)
+ {
+- bool found = TRUE;
+- if (!array_is_empty(&req->db->set->scope)) {
+- found = FALSE;
+- const char *value = auth_fields_find(req->fields, "scope");
+- bool has_scope = value != NULL;
+- if (!has_scope)
+- value = auth_fields_find(req->fields, "aud");
+- e_debug(authdb_event(req->auth_request),
+- "Token scope(s): %s",
+- value);
+- if (value != NULL) {
+- const char *wanted_scope;
+- const char *const *entries = has_scope ?
+- t_strsplit_spaces(value, " ") :
+- t_strsplit_tabescaped(value);
+- array_foreach_elem(&req->db->set->scope, wanted_scope) {
+- if ((found = str_array_find(entries, wanted_scope)))
+- break;
+- }
+- }
+- if (!found) {
+- *error_r = t_strdup_printf("Token is not valid for scope '%s'",
+- req->db->oauth2_set.scope);
+- *result_r = PASSDB_RESULT_USER_DISABLED;
++ if (array_is_empty(&req->db->set->scope))
++ return TRUE;
++
++ bool found = FALSE;
++ const char *value = auth_fields_find(req->fields, "scope");
++ bool has_scope = value != NULL;
++ if (!has_scope)
++ value = auth_fields_find(req->fields, "aud");
++ e_debug(authdb_event(req->auth_request),
++ "Token scope(s): %s",
++ value);
++ if (value != NULL) {
++ const char *wanted_scope;
++ const char *const *entries = has_scope ?
++ t_strsplit_spaces(value, " ") :
++ t_strsplit_tabescaped(value);
++ array_foreach_elem(&req->db->set->scope, wanted_scope) {
++ if ((found = str_array_find(entries, wanted_scope)))
++ break;
+ }
+ }
++ if (!found) {
++ *error_r = t_strdup_printf("Token is not valid for scope '%s'",
++ t_array_const_string_join(&req->db->set->scope, " "));
++ *result_r = PASSDB_RESULT_USER_DISABLED;
++ }
+ return found;
+ }
+
--- /dev/null
+From d1b2e9e028b5bb8501b054c13c5208fbb4eff21f Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 18 Jul 2025 14:26:21 +0300
+Subject: [PATCH] *-commin: Rename LOGIN_PROXY_FAILURE_TYPE_AUTH to
+ LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED
+
+---
+ src/imap-login/imap-proxy.c | 4 ++--
+ src/login-common/client-common-auth.c | 2 +-
+ src/login-common/client-common.c | 2 +-
+ src/login-common/login-proxy.c | 4 ++--
+ src/login-common/login-proxy.h | 2 +-
+ src/pop3-login/pop3-proxy.c | 4 ++--
+ src/submission-login/submission-proxy.c | 4 ++--
+ 7 files changed, 11 insertions(+), 11 deletions(-)
+
+Index: trixie/src/imap-login/imap-proxy.c
+===================================================================
+--- trixie.orig/src/imap-login/imap-proxy.c
++++ trixie/src/imap-login/imap-proxy.c
+@@ -427,7 +427,7 @@ int imap_proxy_parse_line(struct client
+ const char *log_line = line;
+ (void)str_begins_icase(log_line, "NO ", &log_line);
+ enum login_proxy_failure_type failure_type =
+- LOGIN_PROXY_FAILURE_TYPE_AUTH;
++ LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED;
+ #define STR_NO_IMAP_RESP_CODE_AUTHFAILED "NO ["IMAP_RESP_CODE_AUTHFAILED"]"
+ if (str_begins_with(line, STR_NO_IMAP_RESP_CODE_AUTHFAILED)) {
+ /* the remote sent a generic "authentication failed"
+@@ -586,7 +586,7 @@ imap_proxy_send_failure_reply(struct ima
+ client_send_raw(&imap_client->common, t_strconcat(
+ imap_client->cmd_tag, " NO ", reason, "\r\n", NULL));
+ break;
+- case LOGIN_PROXY_FAILURE_TYPE_AUTH:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
+ /* reply was already sent */
+ break;
+ }
+Index: trixie/src/login-common/client-common-auth.c
+===================================================================
+--- trixie.orig/src/login-common/client-common-auth.c
++++ trixie/src/login-common/client-common-auth.c
+@@ -426,7 +426,7 @@ proxy_redirect_reauth_callback(struct au
+ /* Disconnect from the original backend */
+ login_proxy_failed(client->login_proxy,
+ login_proxy_get_event(client->login_proxy),
+- LOGIN_PROXY_FAILURE_TYPE_AUTH,
++ LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED,
+ t_strdup_printf("Redirected to %s", reply.proxy.host));
+ return;
+ }
+Index: trixie/src/login-common/client-common.c
+===================================================================
+--- trixie.orig/src/login-common/client-common.c
++++ trixie/src/login-common/client-common.c
+@@ -1385,7 +1385,7 @@ bool client_get_extra_disconnect_reason(
+ event_reason = "protocol_failure";
+ last_reason = "protocol failure";
+ break;
+- case LOGIN_PROXY_FAILURE_TYPE_AUTH:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
+ event_reason = "auth_failed";
+ last_reason = "authentication failure";
+ break;
+Index: trixie/src/login-common/login-proxy.c
+===================================================================
+--- trixie.orig/src/login-common/login-proxy.c
++++ trixie/src/login-common/login-proxy.c
+@@ -829,7 +829,7 @@ bool login_proxy_failed(struct login_pro
+ case LOGIN_PROXY_FAILURE_TYPE_PROTOCOL:
+ log_prefix = "Remote server sent invalid input: ";
+ break;
+- case LOGIN_PROXY_FAILURE_TYPE_AUTH:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
+ log_prefix = "";
+ try_reconnect = FALSE;
+ break;
+@@ -854,7 +854,7 @@ bool login_proxy_failed(struct login_pro
+ return TRUE;
+ }
+
+- if (type != LOGIN_PROXY_FAILURE_TYPE_AUTH &&
++ if (type != LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED &&
+ type != LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL)
+ e_error(event, "%s%s", log_prefix, reason);
+ else if (proxy->client->set->auth_verbose)
+Index: trixie/src/login-common/login-proxy.h
+===================================================================
+--- trixie.orig/src/login-common/login-proxy.h
++++ trixie/src/login-common/login-proxy.h
+@@ -34,7 +34,7 @@ enum login_proxy_failure_type {
+ LOGIN_PROXY_FAILURE_TYPE_PROTOCOL,
+ /* Authentication failed to backend. The LOGIN/AUTH command reply was
+ already sent to the client. */
+- LOGIN_PROXY_FAILURE_TYPE_AUTH,
++ LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED,
+ /* Authentication failed with a temporary failure code. Attempting it
+ again might work. */
+ LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL,
+Index: trixie/src/pop3-login/pop3-proxy.c
+===================================================================
+--- trixie.orig/src/pop3-login/pop3-proxy.c
++++ trixie/src/pop3-login/pop3-proxy.c
+@@ -322,7 +322,7 @@ int pop3_proxy_parse_line(struct client
+ shouldn't be a real problem since of course everyone will
+ be using only Dovecot as their backend :) */
+ enum login_proxy_failure_type failure_type =
+- LOGIN_PROXY_FAILURE_TYPE_AUTH;
++ LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED;
+ if (!str_begins_with(line, "-ERR ")) {
+ client_send_reply(client, POP3_CMD_REPLY_ERROR,
+ AUTH_FAILED_MSG);
+@@ -373,7 +373,7 @@ pop3_proxy_send_failure_reply(struct cli
+ /* [SYS/TEMP] prefix is already in the reason string */
+ client_send_reply(client, POP3_CMD_REPLY_ERROR, reason);
+ break;
+- case LOGIN_PROXY_FAILURE_TYPE_AUTH:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
+ /* reply was already sent */
+ break;
+ }
+Index: trixie/src/submission-login/submission-proxy.c
+===================================================================
+--- trixie.orig/src/submission-login/submission-proxy.c
++++ trixie/src/submission-login/submission-proxy.c
+@@ -693,7 +693,7 @@ int submission_proxy_parse_line(struct c
+ shouldn't be a real problem since of course everyone will
+ be using only Dovecot as their backend :) */
+ enum login_proxy_failure_type failure_type =
+- LOGIN_PROXY_FAILURE_TYPE_AUTH;
++ LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED;
+ if ((status / 100) == 4)
+ failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL;
+ else if (!submission_proxy_handle_redirect(
+@@ -749,7 +749,7 @@ submission_proxy_send_failure_reply(stru
+ i_assert(subm_client->proxy_reply != NULL);
+ smtp_server_reply_submit(subm_client->proxy_reply);
+ break;
+- case LOGIN_PROXY_FAILURE_TYPE_AUTH:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
+ /* reply was already sent */
+ i_assert(cmd == NULL);
+ break;
--- /dev/null
+From b40043ec2e45b79e9cb185009cffadfb1448da7e Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 24 Mar 2026 12:02:00 +0200
+Subject: [PATCH 1/2] dsync: Avoid potentially excessive data stack growth for
+ mailbox attribute sending
+
+The attribute value can be large.
+---
+ src/doveadm/dsync/dsync-ibc-stream.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/doveadm/dsync/dsync-ibc-stream.c b/src/doveadm/dsync/dsync-ibc-stream.c
+index 472d1fc517..0b3038201d 100644
+--- a/src/doveadm/dsync/dsync-ibc-stream.c
++++ b/src/doveadm/dsync/dsync-ibc-stream.c
+@@ -1492,12 +1492,12 @@ dsync_ibc_stream_send_mailbox_attribute(struct dsync_ibc *_ibc,
+ {
+ struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc;
+ struct dsync_serializer_encoder *encoder;
+- string_t *str = t_str_new(128);
+ char type[2];
+
+ if (ibc->minor_version < DSYNC_PROTOCOL_MINOR_HAVE_ATTRIBUTES)
+ return;
+
++ string_t *str = str_new(default_pool, 128);
+ str_append_c(str, items[ITEM_MAILBOX_ATTRIBUTE].chr);
+ encoder = dsync_ibc_send_encode_begin(ibc, ITEM_MAILBOX_ATTRIBUTE);
+
+@@ -1538,6 +1538,7 @@ dsync_ibc_stream_send_mailbox_attribute(struct dsync_ibc *_ibc,
+ i_stream_ref(ibc->value_output);
+ (void)dsync_ibc_stream_send_value_stream(ibc);
+ }
++ str_free(&str);
+ }
+
+ static enum dsync_ibc_recv_ret
+--
+2.47.3
+
--- /dev/null
+Index: trixie/src/lib-compression/istream-zlib.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-zlib.c
++++ trixie/src/lib-compression/istream-zlib.c
+@@ -32,7 +32,7 @@ struct zlib_istream {
+ bool header_read:1;
+ bool trailer_read:1;
+ bool zs_closed:1;
+- bool starting_concated_output:1;
++ bool starting_concatenated_output:1;
+ };
+
+ static void i_stream_zlib_init(struct zlib_istream *zstream);
+@@ -193,9 +193,9 @@ static ssize_t i_stream_zlib_read(struct
+ return -1;
+ }
+ /* Multiple gz streams concatenated together */
+- zstream->starting_concated_output = TRUE;
++ zstream->starting_concatenated_output = TRUE;
+ }
+- if (zstream->starting_concated_output) {
++ if (zstream->starting_concatenated_output) {
+ /* make sure there actually is something in parent stream.
+ we don't want to reset the stream unless we actually see
+ some concatenated output. */
+@@ -217,7 +217,7 @@ static ssize_t i_stream_zlib_read(struct
+ zstream->header_read = FALSE;
+ zstream->trailer_read = FALSE;
+ zstream->crc32 = 0;
+- zstream->starting_concated_output = FALSE;
++ zstream->starting_concatenated_output = FALSE;
+
+ (void)inflateEnd(&zstream->zs);
+ i_stream_zlib_init(zstream);
--- /dev/null
+From da6075cd4803a36ae06ae0401184aeed128e4320 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 1 May 2026 17:40:08 +0000
+Subject: [PATCH 1/5] imap: Extract side-channel ostream creation into helper
+
+Add client_create_side_channel_output() and use it in cmd_compress()
+in place of the inline channel-creation code. The helper will also be
+used by the imap state import path to recreate the side channel after
+unhibernation. No functional change.
+---
+ src/imap/cmd-compress.c | 10 ++--------
+ src/imap/imap-client.c | 9 +++++++++
+ src/imap/imap-client.h | 5 +++++
+ 3 files changed, 16 insertions(+), 8 deletions(-)
+
+diff --git a/src/imap/cmd-compress.c b/src/imap/cmd-compress.c
+index 363d050f47..18aa988892 100644
+--- a/src/imap/cmd-compress.c
++++ b/src/imap/cmd-compress.c
+@@ -4,7 +4,6 @@
+ #include "imap-commands.h"
+ #include "istream.h"
+ #include "ostream.h"
+-#include "ostream-multiplex.h"
+ #include "iostream-rawlog.h"
+ #include "str.h"
+ #include "strescape.h"
+@@ -92,13 +91,8 @@ bool cmd_compress(struct client_command_context *cmd)
+ /* Let imap-login process handle the COMPRESS. It's the one
+ that will send the tagged reply to the client. */
+ client->compress_handler = handler;
+- if (client->side_channel_output == NULL) {
+- client->side_channel_output =
+- o_stream_multiplex_add_channel(
+- client->multiplex_output, 1);
+- o_stream_set_no_error_handling(
+- client->side_channel_output, TRUE);
+- }
++ if (client->side_channel_output == NULL)
++ client_create_side_channel_output(client);
+ string_t *str = t_str_new(64);
+ str_append(str, "compress\t");
+ str_append_tabescaped(str, handler->name);
+diff --git a/src/imap/imap-client.c b/src/imap/imap-client.c
+index b65a8c9fdf..4c8115f9a0 100644
+--- a/src/imap/imap-client.c
++++ b/src/imap/imap-client.c
+@@ -678,6 +678,15 @@ void client_send_tagline(struct client_command_context *cmd, const char *data)
+ cmd->client->v.send_tagline(cmd, data);
+ }
+
++void client_create_side_channel_output(struct client *client)
++{
++ i_assert(client->multiplex_output != NULL);
++ i_assert(client->side_channel_output == NULL);
++ client->side_channel_output =
++ o_stream_multiplex_add_channel(client->multiplex_output, 1);
++ o_stream_set_no_error_handling(client->side_channel_output, TRUE);
++}
++
+ static void
+ client_default_send_tagline(struct client_command_context *cmd, const char *data)
+ {
+diff --git a/src/imap/imap-client.h b/src/imap/imap-client.h
+index fd1153d97f..a4f2e4f3fb 100644
+--- a/src/imap/imap-client.h
++++ b/src/imap/imap-client.h
+@@ -287,6 +287,11 @@ void client_add_istream_prefix(struct client *client,
+ const unsigned char *data, size_t size);
+ void client_destroy(struct client *client, const char *reason) ATTR_NULL(2);
+
++/* Add the side-channel ostream used to send commands (e.g. dict_reset) back
++ to the imap-login proxy. Must only be called when multiplex_output is set
++ and the channel hasn't been created yet. */
++void client_create_side_channel_output(struct client *client);
++
+ /* Disconnect client connection */
+ void client_disconnect(struct client *client, const char *reason);
+ void client_disconnect_with_error(struct client *client,
+--
+2.47.3
+
--- /dev/null
+From 18f076b165d480cfdf20d81b5683ce7929f02eb4 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Sat, 4 Jul 2026 21:57:15 +0000
+Subject: [PATCH 1/3] imap: Restrict COMPRESS to DEFLATE only
+
+cmd_compress() and the imap-login proxy side channel looked up any
+registered compression handler by name, so a client could request e.g.
+COMPRESS ZSTD even though only COMPRESS=DEFLATE is advertised (RFC 4978).
+
+Non-DEFLATE algorithms can require far more memory to decompress (a zstd
+decoder alone can hold a 16+ MB window per connection versus ~40 KB for
+inflate). This matters now that imap-login handles COMPRESS for proxied
+connections: an attacker opening many connections could drive the shared
+login process into its vsz_limit and crash it, affecting all clients on
+that process.
+
+Only accept DEFLATE on both the backend and the login proxy path.
+---
+ src/imap-login/imap-proxy.c | 11 ++++++-----
+ src/imap/cmd-compress.c | 8 ++++++--
+ 2 files changed, 12 insertions(+), 7 deletions(-)
+
+Index: trixie/src/imap-login/imap-proxy.c
+===================================================================
+--- trixie.orig/src/imap-login/imap-proxy.c
++++ trixie/src/imap-login/imap-proxy.c
+@@ -606,13 +606,14 @@ proxy_side_cmd_compress(struct client *c
+ login_proxy_get_client_ostream(client->login_proxy);
+ const struct compression_handler *handler;
+ int ret = compression_lookup_handler(t_str_lcase(args[0]), &handler);
+- if (ret <= 0) {
+- /* IMAP backend normally checks this already. If we get here,
+- there is a mismatch between what algorithms proxy and
+- backend supports. */
++ /* Only DEFLATE is permitted. The IMAP backend restricts this already;
++ enforce it here too, since imap-login handles COMPRESS for proxied
++ connections and non-DEFLATE algorithms can be far more memory-heavy
++ to decompress. */
++ if (ret <= 0 || strcmp(handler->name, "deflate") != 0) {
+ o_stream_nsend_str(client_output, t_strdup_printf(
+ "%s NO %s compression mechanism\r\n", args[1],
+- ret == 0 ? "Unsupported" : "Unknown"));
++ ret < 0 ? "Unknown" : "Unsupported"));
+ return 0;
+ }
+
+Index: trixie/src/imap/cmd-compress.c
+===================================================================
+--- trixie.orig/src/imap/cmd-compress.c
++++ trixie/src/imap/cmd-compress.c
+@@ -67,10 +67,14 @@ bool cmd_compress(struct client_command_
+ return TRUE;
+ }
+ int ret = compression_lookup_handler(t_str_lcase(mechanism), &handler);
+- if (ret <= 0) {
++ /* Only DEFLATE is advertised (RFC 4978) and accepted here. Other
++ algorithms (e.g. zstd) can require far more memory to decompress,
++ which especially matters now that imap-login can handle COMPRESS for
++ proxied connections and an attacker could open many of them. */
++ if (ret <= 0 || strcmp(handler->name, "deflate") != 0) {
+ const char * tagline =
+ t_strdup_printf("NO %s compression mechanism",
+- ret == 0 ? "Unsupported" : "Unknown");
++ ret < 0 ? "Unknown" : "Unsupported");
+ client_send_tagline(cmd, tagline);
+ return TRUE;
+ }
--- /dev/null
+From b4da247c728ecddc125c2d4021b9b7f70ff1f549 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 16 Apr 2026 23:12:13 +0200
+Subject: [PATCH 1/2] imap-hibernate: Fix out-of-bounds read when parsing DONE
+ command tag
+
+The tag-skipping loop was missing a size>0 guard, so a malformed
+DONE command with no space/CR/tab terminator after the tag would
+read one byte past the end of the buffer. Also add a \0 check to
+stop on embedded null bytes, which are not valid tag characters.
+---
+ src/imap-hibernate/imap-client.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/src/imap-hibernate/imap-client.c b/src/imap-hibernate/imap-client.c
+index 5c11dd1dde..2110fe8e8e 100644
+--- a/src/imap-hibernate/imap-client.c
++++ b/src/imap-hibernate/imap-client.c
+@@ -362,9 +362,11 @@ imap_client_input_parse(const unsigned char *data, size_t size, const char **tag
+ tag_start = data;
+
+ /* skip over tag */
+- while(data[0] != ' ' &&
++ while(size > 0 &&
++ data[0] != ' ' &&
+ data[0] != '\r' &&
+- data[0] != '\t' ) { data++; size--; }
++ data[0] != '\t' &&
++ data[0] != '\0') { data++; size--; }
+
+ tag_end = data;
+
+--
+2.47.3
+
--- /dev/null
+From 277e5c880a7f6a7741e717036570812910fff7b7 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 2 Feb 2026 11:44:58 +0200
+Subject: [PATCH] imap-login: Add comments to internal ID command parameter
+ handling
+
+---
+ src/imap-login/imap-login-cmd-id.c | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/src/imap-login/imap-login-cmd-id.c b/src/imap-login/imap-login-cmd-id.c
+index 1cd30bd1bc..e51e9c1e72 100644
+--- a/src/imap-login/imap-login-cmd-id.c
++++ b/src/imap-login/imap-login-cmd-id.c
+@@ -183,8 +183,13 @@ static bool cmd_id_handle_keyvalue(struct imap_client *client,
+ imap_id_param_handler_find(key);
+ bool is_login_id_param = handler != NULL;
+
+- if (is_login_id_param && client->common.connection_trusted &&
+- !client->id_logged && value != NULL) {
++ if (!is_login_id_param) {
++ /* not an internal key */
++ } else if (client->id_logged) {
++ /* using ID multiple times - ignore */
++ } else if (value == NULL) {
++ /* there should have been a value - ignore */
++ } else if (client->common.connection_trusted) {
+ if (!handler->callback(client->cmd_id->params, key, value)) {
+ e_debug(client->common.event,
+ "Client sent invalid ID parameter '%s'", key);
+--
+2.47.3
+
--- /dev/null
+From 4f3471057a754d93af368527e08af3db3d3e82c9 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Sat, 4 Jul 2026 16:04:04 +0000
+Subject: [PATCH] imap-login: Fix excessive memory growth with pre-login ID
+ command
+
+The pre-login ID parser reset the per-argument line and list limits after
+every argument, so nothing bounded the number of key/value pairs. Each
+external key grew log_reply and added a distinct event field (a linear
+scan, making it O(N^2)), allowing a pre-auth CPU/memory amplification.
+
+Account at most 30 pairs (per RFC 2971) into the logging and client_id
+bookkeeping. Internal x-* parameter handlers still run for pairs beyond
+the limit, since a trusted proxy may legitimately forward more fields.
+---
+ src/imap-login/imap-login-client.h | 4 ++++
+ src/imap-login/imap-login-cmd-id.c | 20 ++++++++++++++++++--
+ 2 files changed, 22 insertions(+), 2 deletions(-)
+
+Index: trixie/src/imap-login/imap-login-client.h
+===================================================================
+--- trixie.orig/src/imap-login/imap-login-client.h
++++ trixie/src/imap-login/imap-login-client.h
+@@ -53,6 +53,10 @@ struct imap_client_cmd_id {
+ struct event *params_event;
+ struct imap_id_params *params;
+ string_t *log_reply;
++ /* Number of external (non internal x-*) key/value pairs accounted so
++ far. Used to bound the pre-login accounting (logging, event fields,
++ client_id) done per external pair. */
++ unsigned int processed_pairs_count;
+ };
+
+ struct imap_client {
+Index: trixie/src/imap-login/imap-login-cmd-id.c
+===================================================================
+--- trixie.orig/src/imap-login/imap-login-cmd-id.c
++++ trixie/src/imap-login/imap-login-cmd-id.c
+@@ -9,6 +9,12 @@
+ #include "imap-login-settings.h"
+ #include "imap-login-client.h"
+
++/* RFC 2971 says a client SHOULD NOT send more than 30 field-value pairs.
++ Allow that many to be accounted for (logged / kept in client_id); pairs
++ beyond this are still parsed and internal x-* handlers still run, but they
++ are not added to the logging/client_id accounting that grows per pair. */
++#define IMAP_ID_MAX_ACCOUNTED_PAIRS 30
++
+ struct imap_id_params_forward {
+ const char *key;
+ const char *value;
+@@ -183,6 +189,16 @@ static bool cmd_id_handle_keyvalue(struc
+ imap_id_param_handler_find(key);
+ bool is_login_id_param = handler != NULL;
+
++ /* Only external pairs are logged / kept in client_id, and only those
++ grow per-pair with count, so only they need bounding to avoid a
++ pre-login CPU/memory DoS. Internal x-* handlers must keep running
++ regardless (e.g. a proxy may legitimately forward more than 30
++ fields) and are not counted against the limit. */
++ bool account_pair = !is_login_id_param &&
++ client->cmd_id->processed_pairs_count < IMAP_ID_MAX_ACCOUNTED_PAIRS;
++ if (account_pair)
++ client->cmd_id->processed_pairs_count++;
++
+ if (!is_login_id_param) {
+ /* not an internal key */
+ } else if (client->id_logged) {
+@@ -197,7 +213,7 @@ static bool cmd_id_handle_keyvalue(struc
+ }
+ }
+
+- if (client->set->imap_id_retain && !is_login_id_param &&
++ if (account_pair && client->set->imap_id_retain &&
+ (client->common.client_id == NULL ||
+ str_len(client->common.client_id) + kvlen < LOGIN_MAX_CLIENT_ID_LEN)) {
+ if (client->common.client_id == NULL) {
+@@ -213,7 +229,7 @@ static bool cmd_id_handle_keyvalue(struc
+ imap_append_quoted(client->common.client_id, value);
+ }
+
+- if (!is_login_id_param)
++ if (account_pair)
+ imap_id_add_log_entry(log_entry, key, value);
+ return TRUE;
+ }
--- /dev/null
+From 0d490d6453b4779bc711f1b998e6d09ae2aab8a8 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 26 May 2025 09:37:35 +0300
+Subject: [PATCH] lda: Fix using USER environment if -d hasn't been specified
+
+This became broken at some point.
+---
+ src/lda/main.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+Index: dovecot/src/lda/main.c
+===================================================================
+--- dovecot.orig/src/lda/main.c
++++ dovecot/src/lda/main.c
+@@ -453,9 +453,6 @@ int main(int argc, char *argv[])
+ i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[optind]);
+ }
+
+- if (master_service_settings_read_simple(master_service, &error) < 0)
+- i_fatal("%s", error);
+-
+ process_euid = geteuid();
+ if ((service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0)
+ ;
+@@ -485,6 +482,13 @@ int main(int argc, char *argv[])
+ i_fatal_status(EX_USAGE,
+ "destination user parameter (-d user) not given");
+ }
++ struct master_service_settings_input set_input = {
++ .preserve_user = TRUE,
++ };
++ struct master_service_settings_output set_output;
++ if (master_service_settings_read(master_service, &set_input,
++ &set_output, &error) < 0)
++ i_fatal("%s", error);
+ master_service_init_finish(master_service);
+
+ dinput.mail_from = mail_from;
--- /dev/null
+From 972acc9f9e73a8bc5b029991fb3752d9d16ef632 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 13 Apr 2026 12:30:03 +0200
+Subject: [PATCH 01/12] lib: Add XXH64 hash implementation
+
+Adds a streaming XXH64 (non-cryptographic) hash with init/loop/result
+API, a one-shot xxh64_data(), and an xxh64_to_32() XOR-fold inline.
+Registered in hash_methods[] for use via the generic hash_method API.
+---
+ src/lib/Makefile.am | 2 +
+ src/lib/hash-method.c | 2 +
+ src/lib/test-hash-method.c | 33 +++++++
+ src/lib/xxh64.c | 184 +++++++++++++++++++++++++++++++++++++
+ src/lib/xxh64.h | 30 ++++++
+ 5 files changed, 251 insertions(+)
+ create mode 100644 src/lib/xxh64.c
+ create mode 100644 src/lib/xxh64.h
+
+diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
+index 6da8a308e5..eb9b9ddda3 100644
+--- a/src/lib/Makefile.am
++++ b/src/lib/Makefile.am
+@@ -177,6 +177,7 @@ liblib_la_SOURCES = \
+ sha1.c \
+ sha2.c \
+ sha3.c \
++ xxh64.c \
+ sleep.c \
+ sort.c \
+ stats-dist.c \
+@@ -337,6 +338,7 @@ headers = \
+ sha1.h \
+ sha2.h \
+ sha3.h \
++ xxh64.h \
+ sleep.h \
+ sort.h \
+ stats-dist.h \
+diff --git a/src/lib/hash-method.c b/src/lib/hash-method.c
+index d7a6bed0ea..bdeefbadc6 100644
+--- a/src/lib/hash-method.c
++++ b/src/lib/hash-method.c
+@@ -6,6 +6,7 @@
+ #include "sha1.h"
+ #include "sha2.h"
+ #include "sha3.h"
++#include "xxh64.h"
+ #include "hash-method.h"
+
+ const struct hash_method *hash_method_lookup(const char *name)
+@@ -93,6 +94,7 @@ const struct hash_method *hash_methods[] = {
+ &hash_method_sha512,
+ &hash_method_sha3_256,
+ &hash_method_sha3_512,
++ &hash_method_xxh64,
+ &hash_method_size,
+ NULL
+ };
+diff --git a/src/lib/test-hash-method.c b/src/lib/test-hash-method.c
+index 6592ec90e6..6384dc99ca 100644
+--- a/src/lib/test-hash-method.c
++++ b/src/lib/test-hash-method.c
+@@ -425,6 +425,39 @@ static void test_hash_methods_fips() {
+ "\x6d\xf6\x54\x5a\x1c\xe8\xba\x00",
+ 512 / 8,
+ },
++ /* xxh64 reference vectors (seed=0) */
++ { "xxh64",
++ "",
++ 0,
++ 1,
++ "\xef\x46\xdb\x37\x51\xd8\xe9\x99",
++ 64 / 8
++ },
++ { "xxh64",
++ "abc",
++ 3,
++ 1,
++ "\x44\xbc\x2c\xf5\xad\x77\x09\x99",
++ 64 / 8
++ },
++ /* 62 bytes: exercises the >= 32-byte accumulator path */
++ { "xxh64",
++ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
++ "ghijklmnopqrstuvwxyz0123456789",
++ 62,
++ 1,
++ "\xba\x76\x1a\x9a\xa8\x0e\x0d\x36",
++ 64 / 8
++ },
++ /* 16 bytes × 3 rounds: exercises chunked input across the 32-byte block boundary */
++ { "xxh64",
++ "\x00\x01\x02\x03\x04\x05\x06\x07"
++ "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++ 16,
++ 3,
++ "\x32\x0d\xcd\xab\xd8\x29\x7a\x9e",
++ 64 / 8
++ },
+ };
+
+ for(size_t i = 0; i < N_ELEMENTS(test_vectors); i++) {
+diff --git a/src/lib/xxh64.c b/src/lib/xxh64.c
+new file mode 100644
+index 0000000000..1c17833afd
+--- /dev/null
++++ b/src/lib/xxh64.c
+@@ -0,0 +1,184 @@
++/* Copyright (c) 2025 Dovecot authors, see the included COPYING file */
++
++/* XXHash64 - Fast non-cryptographic hash function.
++ * Based on Yann Collet's public domain XXHash64 algorithm.
++ * Reference: https://github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md
++ */
++
++#include "lib.h"
++#include "xxh64.h"
++
++#define XXH64_PRIME1 UINT64_C(0x9E3779B185EBCA87)
++#define XXH64_PRIME2 UINT64_C(0xC2B2AE3D27D4EB4F)
++#define XXH64_PRIME3 UINT64_C(0x165667B19E3779F9)
++#define XXH64_PRIME4 UINT64_C(0x85EBCA77C2B27589)
++#define XXH64_PRIME5 UINT64_C(0x27D4EB2F165667C5)
++
++#define XXH64_ROTL(x, r) (((x) << (r)) | ((x) >> (64 - (r))))
++
++static inline uint64_t xxh64_read64(const void *p)
++{
++ uint64_t v;
++ memcpy(&v, p, sizeof(v));
++ return v;
++}
++
++static inline uint32_t xxh64_read32(const void *p)
++{
++ uint32_t v;
++ memcpy(&v, p, sizeof(v));
++ return v;
++}
++
++static uint64_t ATTR_UNSIGNED_WRAPS xxh64_round(uint64_t acc, uint64_t input)
++{
++ acc += input * XXH64_PRIME2;
++ acc = XXH64_ROTL(acc, 31);
++ acc *= XXH64_PRIME1;
++ return acc;
++}
++
++static uint64_t ATTR_UNSIGNED_WRAPS xxh64_merge_acc(uint64_t h64, uint64_t acc)
++{
++ acc = xxh64_round(0, acc);
++ h64 ^= acc;
++ h64 = h64 * XXH64_PRIME1 + XXH64_PRIME4;
++ return h64;
++}
++
++void ATTR_UNSIGNED_WRAPS xxh64_init(struct xxh64_context *ctx, uint64_t seed)
++{
++ ctx->seed = seed;
++ ctx->v1 = seed + XXH64_PRIME1 + XXH64_PRIME2;
++ ctx->v2 = seed + XXH64_PRIME2;
++ ctx->v3 = seed;
++ ctx->v4 = seed - XXH64_PRIME1;
++ ctx->total_len = 0;
++ ctx->buf_used = 0;
++}
++
++void ATTR_UNSIGNED_WRAPS xxh64_loop(struct xxh64_context *ctx, const void *data, size_t size)
++{
++ const unsigned char *p = data;
++ const unsigned char *end = p + size;
++
++ ctx->total_len += size;
++
++ if (ctx->buf_used + size < 32) {
++ memcpy(ctx->buf + ctx->buf_used, p, size);
++ ctx->buf_used += size;
++ return;
++ }
++
++ if (ctx->buf_used > 0) {
++ size_t fill = 32 - ctx->buf_used;
++ memcpy(ctx->buf + ctx->buf_used, p, fill);
++ p += fill;
++ ctx->v1 = xxh64_round(ctx->v1, xxh64_read64(ctx->buf));
++ ctx->v2 = xxh64_round(ctx->v2, xxh64_read64(ctx->buf + 8));
++ ctx->v3 = xxh64_round(ctx->v3, xxh64_read64(ctx->buf + 16));
++ ctx->v4 = xxh64_round(ctx->v4, xxh64_read64(ctx->buf + 24));
++ ctx->buf_used = 0;
++ }
++
++ while (p + 32 <= end) {
++ ctx->v1 = xxh64_round(ctx->v1, xxh64_read64(p));
++ ctx->v2 = xxh64_round(ctx->v2, xxh64_read64(p + 8));
++ ctx->v3 = xxh64_round(ctx->v3, xxh64_read64(p + 16));
++ ctx->v4 = xxh64_round(ctx->v4, xxh64_read64(p + 24));
++ p += 32;
++ }
++
++ if (p < end) {
++ ctx->buf_used = (unsigned int)(end - p);
++ memcpy(ctx->buf, p, ctx->buf_used);
++ }
++}
++
++uint64_t ATTR_UNSIGNED_WRAPS xxh64_result(struct xxh64_context *ctx)
++{
++ const unsigned char *p = ctx->buf;
++ const unsigned char *end = p + ctx->buf_used;
++ uint64_t h64;
++
++ if (ctx->total_len >= 32) {
++ h64 = XXH64_ROTL(ctx->v1, 1) + XXH64_ROTL(ctx->v2, 7) +
++ XXH64_ROTL(ctx->v3, 12) + XXH64_ROTL(ctx->v4, 18);
++ h64 = xxh64_merge_acc(h64, ctx->v1);
++ h64 = xxh64_merge_acc(h64, ctx->v2);
++ h64 = xxh64_merge_acc(h64, ctx->v3);
++ h64 = xxh64_merge_acc(h64, ctx->v4);
++ } else {
++ h64 = ctx->seed + XXH64_PRIME5;
++ }
++
++ h64 += ctx->total_len;
++
++ while (p + 8 <= end) {
++ h64 ^= xxh64_round(0, xxh64_read64(p));
++ h64 = XXH64_ROTL(h64, 27) * XXH64_PRIME1 + XXH64_PRIME4;
++ p += 8;
++ }
++ if (p + 4 <= end) {
++ h64 ^= (uint64_t)xxh64_read32(p) * XXH64_PRIME1;
++ h64 = XXH64_ROTL(h64, 23) * XXH64_PRIME2 + XXH64_PRIME3;
++ p += 4;
++ }
++ while (p < end) {
++ h64 ^= (uint64_t)*p * XXH64_PRIME5;
++ h64 = XXH64_ROTL(h64, 11) * XXH64_PRIME1;
++ p++;
++ }
++
++ /* Avalanche */
++ h64 ^= h64 >> 33;
++ h64 *= XXH64_PRIME2;
++ h64 ^= h64 >> 29;
++ h64 *= XXH64_PRIME3;
++ h64 ^= h64 >> 32;
++
++ return h64;
++}
++
++uint64_t xxh64_data(const void *data, size_t size, uint64_t seed)
++{
++ struct xxh64_context ctx;
++
++ xxh64_init(&ctx, seed);
++ xxh64_loop(&ctx, data, size);
++ return xxh64_result(&ctx);
++}
++
++static void xxh64_hash_init(void *context)
++{
++ xxh64_init(context, 0);
++}
++
++static void xxh64_hash_loop(void *context, const void *data, size_t size)
++{
++ xxh64_loop(context, data, size);
++}
++
++static void xxh64_hash_result(void *context, unsigned char *digest_r)
++{
++ uint64_t hash = xxh64_result(context);
++
++ digest_r[0] = (hash >> 56) & 0xff;
++ digest_r[1] = (hash >> 48) & 0xff;
++ digest_r[2] = (hash >> 40) & 0xff;
++ digest_r[3] = (hash >> 32) & 0xff;
++ digest_r[4] = (hash >> 24) & 0xff;
++ digest_r[5] = (hash >> 16) & 0xff;
++ digest_r[6] = (hash >> 8) & 0xff;
++ digest_r[7] = (hash >> 0) & 0xff;
++}
++
++const struct hash_method hash_method_xxh64 = {
++ .name = "xxh64",
++ .block_size = 32,
++ .context_size = sizeof(struct xxh64_context),
++ .digest_size = XXH64_RESULTLEN,
++ .init = xxh64_hash_init,
++ .loop = xxh64_hash_loop,
++ .result = xxh64_hash_result,
++};
+diff --git a/src/lib/xxh64.h b/src/lib/xxh64.h
+new file mode 100644
+index 0000000000..7beb4cafb4
+--- /dev/null
++++ b/src/lib/xxh64.h
+@@ -0,0 +1,30 @@
++#ifndef XXH64_H
++#define XXH64_H
++
++#include "hash-method.h"
++
++#define XXH64_RESULTLEN 8
++
++struct xxh64_context {
++ uint64_t seed;
++ uint64_t v1, v2, v3, v4;
++ uint64_t total_len;
++ unsigned char buf[32];
++ unsigned int buf_used;
++};
++
++void xxh64_init(struct xxh64_context *ctx, uint64_t seed);
++void xxh64_loop(struct xxh64_context *ctx, const void *data, size_t size);
++uint64_t xxh64_result(struct xxh64_context *ctx);
++
++uint64_t xxh64_data(const void *data, size_t size, uint64_t seed) ATTR_PURE;
++
++/* XOR-fold the 64-bit hash to 32 bits */
++static inline uint32_t xxh64_to_32(uint64_t hash)
++{
++ return (uint32_t)(hash ^ (hash >> 32));
++}
++
++extern const struct hash_method hash_method_xxh64;
++
++#endif
+--
+2.47.3
+
--- /dev/null
+From 09237b986e2a5710a562a9750a6df1aef6d28e69 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 28 Apr 2026 01:50:56 +0300
+Subject: [PATCH 1/2] lib: Add str_equals_timing_safe()
+
+Constant-time string comparison that avoids the length leak in
+str_equals_timing_almost_safe(). Compares HMAC-SHA256 digests of the
+inputs (keyed with hash_iv) rather than the strings themselves, so
+neither the contents nor the length of either input affects timing in
+a way an attacker can exploit.
+---
+ src/lib/strfuncs.c | 29 +++++++++++++++++++++++++++++
+ src/lib/strfuncs.h | 5 +++++
+ src/lib/test-strfuncs.c | 27 +++++++++++++++++++++++++++
+ 3 files changed, 61 insertions(+)
+
+Index: dovecot/src/lib/strfuncs.c
+===================================================================
+--- dovecot.orig/src/lib/strfuncs.c
++++ dovecot/src/lib/strfuncs.c
+@@ -7,6 +7,9 @@
+ #include "printf-format-fix.h"
+ #include "strfuncs.h"
+ #include "array.h"
++#include "hash.h"
++#include "hmac.h"
++#include "sha2.h"
+
+ #include <stdio.h>
+ #include <limits.h>
+@@ -627,6 +630,32 @@ bool str_equals_timing_almost_safe(const
+ return ret == 0;
+ }
+
++bool str_equals_hash_timing_safe(const char *s1, const char *s2)
++{
++ struct hmac_context ctx;
++ unsigned char digest1[SHA256_RESULTLEN];
++ unsigned char digest2[SHA256_RESULTLEN];
++
++ /* Compare HMAC-SHA256 digests of the inputs rather than the inputs
++ themselves. The digest length is constant, so the subsequent
++ mem_equals_timing_safe() leaks no length information. The HMAC
++ times depend on the input lengths, but each side's length is either
++ a deployment constant (for the secret) or already known to the
++ attacker (for their own input), so neither leaks a useful signal.
++ hash_iv keys the HMAC to prevent precomputation. */
++ hmac_init(&ctx, (const unsigned char *)&hash_iv, sizeof(hash_iv),
++ &hash_method_sha256);
++ hmac_update(&ctx, s1, strlen(s1));
++ hmac_final(&ctx, digest1);
++
++ hmac_init(&ctx, (const unsigned char *)&hash_iv, sizeof(hash_iv),
++ &hash_method_sha256);
++ hmac_update(&ctx, s2, strlen(s2));
++ hmac_final(&ctx, digest2);
++
++ return mem_equals_timing_safe(digest1, digest2, sizeof(digest1));
++}
++
+ size_t
+ str_match(const char *p1, const char *p2)
+ {
+Index: dovecot/src/lib/strfuncs.h
+===================================================================
+--- dovecot.orig/src/lib/strfuncs.h
++++ dovecot/src/lib/strfuncs.h
+@@ -93,6 +93,11 @@ bool mem_equals_timing_safe(const void *
+ the string lengths are the same. If not, the length of the secret string may
+ be leaked, but otherwise the contents won't be. */
+ bool str_equals_timing_almost_safe(const char *s1, const char *s2);
++/* Returns TRUE if the two strings are equal. Safe against timing attacks:
++ neither the contents nor the length of either string is leaked.
++ Implemented by HMAC-SHA256ing both inputs under a random per-process key
++ and comparing the fixed-length digests. */
++bool str_equals_hash_timing_safe(const char *s1, const char *s2);
+
+ size_t str_match(const char *p1, const char *p2) ATTR_PURE;
+ size_t str_match_icase(const char *p1, const char *p2) ATTR_PURE;
+Index: dovecot/src/lib/test-strfuncs.c
+===================================================================
+--- dovecot.orig/src/lib/test-strfuncs.c
++++ dovecot/src/lib/test-strfuncs.c
+@@ -478,6 +478,32 @@ static void test_str_equals_timing_almos
+ test_end();
+ }
+
++static void test_str_equals_hash_timing_safe(void)
++{
++ const struct {
++ const char *a, *b;
++ } tests[] = {
++ { "", "" },
++ { "a", "a" },
++ { "b", "a" },
++ { "ab", "ab" },
++ { "ab", "ba" },
++ { "ab", "bc" },
++ { "a", "" },
++ { "a", "ab" },
++ { "a", "abc" },
++ { "ab", "abc" },
++ };
++ test_begin("str_equals_hash_timing_safe()");
++ for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
++ test_assert((strcmp(tests[i].a, tests[i].b) == 0) ==
++ str_equals_hash_timing_safe(tests[i].a, tests[i].b));
++ test_assert((strcmp(tests[i].a, tests[i].b) == 0) ==
++ str_equals_hash_timing_safe(tests[i].b, tests[i].a));
++ }
++ test_end();
++}
++
+ static void test_dec2str_buf(void)
+ {
+ const uintmax_t test_input[] = {
+@@ -773,6 +799,7 @@ void test_strfuncs(void)
+ test_p_array_const_string_join();
+ test_mem_equals_timing_safe();
+ test_str_equals_timing_almost_safe();
++ test_str_equals_hash_timing_safe();
+ test_dec2str_buf();
+ test_str_match();
+ test_str_match_icase();
--- /dev/null
+From cac140a9e4e12485bde4348b8a8c94a7a96a6e5e Mon Sep 17 00:00:00 2001
+From: Michael M Slusarz <michael.slusarz@open-xchange.com>
+Date: Sat, 4 Oct 2025 12:04:31 -0600
+Subject: [PATCH] lib-compression: Don't calculate crc32 if using zlib in
+ deflate mode
+
+---
+ src/lib-compression/istream-zlib.c | 8 +++++---
+ src/lib-compression/ostream-zlib.c | 4 +++-
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+Index: trixie/src/lib-compression/istream-zlib.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-zlib.c
++++ trixie/src/lib-compression/istream-zlib.c
+@@ -264,9 +264,11 @@ static ssize_t i_stream_zlib_read(struct
+ ret = inflate(&zstream->zs, Z_SYNC_FLUSH);
+
+ out_size -= zstream->zs.avail_out;
+- zstream->crc32 = crc32_data_more(zstream->crc32,
+- stream->w_buffer + stream->pos,
+- out_size);
++ /* CRC32 is only needed for GZ trailer. */
++ if (zstream->gz)
++ zstream->crc32 = crc32_data_more(zstream->crc32,
++ stream->w_buffer + stream->pos,
++ out_size);
+ stream->pos += out_size;
+
+ size_t bytes_consumed = size - zstream->zs.avail_in;
+Index: trixie/src/lib-compression/ostream-zlib.c
+===================================================================
+--- trixie.orig/src/lib-compression/ostream-zlib.c
++++ trixie/src/lib-compression/ostream-zlib.c
+@@ -219,7 +219,9 @@ o_stream_zlib_send_chunk(struct zlib_ost
+ }
+ size -= zs->avail_in;
+
+- zstream->crc = crc32_data_more(zstream->crc, data, size);
++ /* CRC32 is only needed for GZ trailer. */
++ if (zstream->gz)
++ zstream->crc = crc32_data_more(zstream->crc, data, size);
+ zstream->bytes32 += size;
+ zstream->flushed = FALSE;
+ return size;
--- /dev/null
+From 86aba25a1159f7ff11480feec287647be78054d1 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Mon, 23 Feb 2026 13:52:36 +0200
+Subject: [PATCH] lib-compression: istream-lz4 - Ensure uncompressed chunk size
+ is not 0
+
+---
+ src/lib-compression/istream-lz4.c | 5 ++-
+ src/lib-compression/test-compression.c | 52 ++++++++++++++++++++++++++
+ 2 files changed, 55 insertions(+), 2 deletions(-)
+
+Index: trixie/src/lib-compression/istream-lz4.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-lz4.c
++++ trixie/src/lib-compression/istream-lz4.c
+@@ -77,9 +77,10 @@ static int i_stream_lz4_read_header(stru
+ zstream->max_uncompressed_chunk_size =
+ be32_to_cpu_unaligned(hdr->max_uncompressed_chunk_size);
+ buffer_set_used_size(zstream->chunk_buf, 0);
+- if (zstream->max_uncompressed_chunk_size > ISTREAM_LZ4_CHUNK_SIZE) {
++ if (zstream->max_uncompressed_chunk_size == 0 ||
++ zstream->max_uncompressed_chunk_size > ISTREAM_LZ4_CHUNK_SIZE) {
+ lz4_read_error(zstream, t_strdup_printf(
+- "lz4 max chunk size too large (%u > %u)",
++ "invalid lz4 max chunk size (%u, max %u)",
+ zstream->max_uncompressed_chunk_size,
+ ISTREAM_LZ4_CHUNK_SIZE));
+ zstream->istream.istream.stream_errno = EINVAL;
+Index: trixie/src/lib-compression/test-compression.c
+===================================================================
+--- trixie.orig/src/lib-compression/test-compression.c
++++ trixie/src/lib-compression/test-compression.c
+@@ -1040,6 +1040,57 @@ static void test_lz4_small_header(void)
+ test_end();
+ }
+
++static const unsigned char lz4_chunk_crash_01[] = {
++ 0x44, 0x6f, 0x76, 0x65, 0x63, 0x6f, 0x74, 0x2d, 0x4c, 0x5a, 0x34, 0x0d,
++ 0x2a, 0x9b, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x7a,
++ 0x32, 0x01, 0xff, 0xff, 0x00, 0x30
++};
++
++static const unsigned char lz4_chunk_crash_02[] = {
++ 0x44, 0x6f, 0x76, 0x65, 0x63, 0x6f, 0x74, 0x2d, 0x4c, 0x5a, 0x34, 0x0d,
++ 0x2a, 0x9b, 0xc5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x7a,
++ 0x32, 0x01, 0xff, 0xff, 0x00, 0x30
++};
++
++static void test_lz4_chunk_size(void)
++{
++ const struct compression_handler *lz4;
++ struct istream *file_input, *input;
++
++ if (compression_lookup_handler("lz4", &lz4) <= 0)
++ return; /* not compiled in or unknown */
++
++ test_begin("lz4 chunk size");
++
++ file_input = test_istream_create_data(lz4_chunk_crash_01,
++ sizeof(lz4_chunk_crash_01));
++ input = lz4->create_istream(file_input);
++ i_stream_unref(&file_input);
++ i_stream_read(input);
++
++ test_assert(input->eof);
++ test_assert(input->stream_errno != 0);
++ const char *error = i_stream_get_error(input);
++ test_assert(strstr(error, "invalid lz4 max chunk size") != NULL);
++
++ i_stream_unref(&input);
++
++ file_input = test_istream_create_data(lz4_chunk_crash_02,
++ sizeof(lz4_chunk_crash_02));
++ input = lz4->create_istream(file_input);
++ i_stream_unref(&file_input);
++ i_stream_read(input);
++
++ test_assert(input->eof);
++ test_assert(input->stream_errno != 0);
++ error = i_stream_get_error(input);
++ test_assert(strstr(error, "invalid lz4 max chunk size") != NULL);
++
++ i_stream_unref(&input);
++
++ test_end();
++}
++
+ static void test_uncompress_file(const char *path)
+ {
+ const struct compression_handler *handler;
+@@ -1163,6 +1214,7 @@ int main(int argc, char *argv[])
+ test_gz_header,
+ test_gz_large_header,
+ test_lz4_small_header,
++ test_lz4_chunk_size,
+ test_compression_ext,
+ test_compression_deinit,
+ NULL
--- /dev/null
+From f269b3d3051044aec31cef888fcf4c9ddd21c227 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Mon, 23 Feb 2026 14:09:02 +0200
+Subject: [PATCH] lib-compression: istream-lz4 - Try again if no data was
+ decompressed
+
+---
+ src/lib-compression/istream-lz4.c | 3 ++-
+ src/lib-compression/test-compression.c | 17 +++++++++++++++++
+ 2 files changed, 19 insertions(+), 1 deletion(-)
+
+Index: trixie/src/lib-compression/istream-lz4.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-lz4.c
++++ trixie/src/lib-compression/istream-lz4.c
+@@ -198,7 +198,8 @@ static ssize_t i_stream_lz4_read(struct
+ lz4_read_error(zstream, "corrupted lz4 chunk");
+ stream->istream.stream_errno = EINVAL;
+ return -1;
+- }
++ } else if (ret == 0)
++ return i_stream_lz4_read(stream);
+ i_assert(ret > 0);
+ stream->pos += ret;
+ i_assert(stream->pos <= stream->buffer_size);
+Index: trixie/src/lib-compression/test-compression.c
+===================================================================
+--- trixie.orig/src/lib-compression/test-compression.c
++++ trixie/src/lib-compression/test-compression.c
+@@ -1052,6 +1052,11 @@ static const unsigned char lz4_chunk_cra
+ 0x32, 0x01, 0xff, 0xff, 0x00, 0x30
+ };
+
++static const unsigned char lz4_chunk_eof_03[] = {
++ 0x44, 0x6f, 0x76, 0x65, 0x63, 0x6f, 0x74, 0x2d, 0x4c, 0x5a, 0x34, 0x0d,
++ 0x2a, 0x9b, 0xc5, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
++};
++
+ static void test_lz4_chunk_size(void)
+ {
+ const struct compression_handler *lz4;
+@@ -1088,6 +1093,18 @@ static void test_lz4_chunk_size(void)
+
+ i_stream_unref(&input);
+
++ file_input = test_istream_create_data(lz4_chunk_eof_03,
++ sizeof(lz4_chunk_eof_03));
++ input = lz4->create_istream(file_input);
++ i_stream_unref(&file_input);
++ i_stream_read(input);
++
++ /* no error, but no content either */
++ test_assert(input->eof);
++ test_assert(input->stream_errno == 0);
++
++ i_stream_unref(&input);
++
+ test_end();
+ }
+
--- /dev/null
+From 09df3b5b8b970439ab6b1b617b344a21cb2c07fe Mon Sep 17 00:00:00 2001
+From: Stephan Bosch <stephan.bosch@open-xchange.com>
+Date: Tue, 14 Nov 2023 16:57:45 +0100
+Subject: [PATCH] lib-compression: istream-zlib - Use container_of() macro
+
+---
+ src/lib-compression/istream-zlib.c | 12 ++++++++----
+ 1 file changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/src/lib-compression/istream-zlib.c b/src/lib-compression/istream-zlib.c
+index 93b3473071..0a1832a66d 100644
+--- a/src/lib-compression/istream-zlib.c
++++ b/src/lib-compression/istream-zlib.c
+@@ -40,7 +40,8 @@ static void i_stream_zlib_init(struct zlib_istream *zstream);
+ static void i_stream_zlib_close(struct iostream_private *stream,
+ bool close_parent)
+ {
+- struct zlib_istream *zstream = (struct zlib_istream *)stream;
++ struct zlib_istream *zstream =
++ container_of(stream, struct zlib_istream, istream.iostream);
+
+ if (!zstream->zs_closed) {
+ (void)inflateEnd(&zstream->zs);
+@@ -160,7 +161,8 @@ static int i_stream_zlib_read_trailer(struct zlib_istream *zstream)
+
+ static ssize_t i_stream_zlib_read(struct istream_private *stream)
+ {
+- struct zlib_istream *zstream = (struct zlib_istream *)stream;
++ struct zlib_istream *zstream =
++ container_of(stream, struct zlib_istream, istream);
+ const unsigned char *data;
+ uoff_t high_offset;
+ size_t size, out_size;
+@@ -362,7 +364,8 @@ static void i_stream_zlib_reset(struct zlib_istream *zstream)
+ static void
+ i_stream_zlib_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
+ {
+- struct zlib_istream *zstream = (struct zlib_istream *) stream;
++ struct zlib_istream *zstream =
++ container_of(stream, struct zlib_istream, istream);
+
+ if (i_stream_nonseekable_try_seek(stream, v_offset))
+ return;
+@@ -378,7 +381,8 @@ i_stream_zlib_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
+
+ static void i_stream_zlib_sync(struct istream_private *stream)
+ {
+- struct zlib_istream *zstream = (struct zlib_istream *) stream;
++ struct zlib_istream *zstream =
++ container_of(stream, struct zlib_istream, istream);
+ const struct stat *st;
+
+ if (i_stream_stat(stream->parent, FALSE, &st) == 0) {
+--
+2.47.3
+
--- /dev/null
+From aa8e26f7879895ff55bc247fb2594ffb3c34f4bc Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Wed, 10 Jun 2026 11:53:39 +0000
+Subject: [PATCH 1/4] lib-compression: wrap *_read() in for(;;) loop (reindent
+ only)
+
+Prepare for the next commit: wrap the body of i_stream_lz4_read(),
+i_stream_bzlib_read(), and i_stream_zlib_read() in a for(;;) loop.
+No logic change; all paths still return on the first iteration.
+Separating the indent churn makes the actual fix easier to review.
+---
+ src/lib-compression/istream-bzlib.c | 158 ++++++++---------
+ src/lib-compression/istream-lz4.c | 114 ++++++------
+ src/lib-compression/istream-zlib.c | 260 ++++++++++++++--------------
+ 3 files changed, 269 insertions(+), 263 deletions(-)
+
+Index: trixie/src/lib-compression/istream-bzlib.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-bzlib.c
++++ trixie/src/lib-compression/istream-bzlib.c
+@@ -51,88 +51,90 @@ static ssize_t i_stream_bzlib_read(struc
+ size_t size, out_size;
+ int ret;
+
+- high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
+- if (zstream->eof_offset == high_offset) {
+- stream->istream.eof = TRUE;
+- return -1;
+- }
+-
+- if (!zstream->marked) {
+- if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size))
+- return -2; /* buffer full */
+- } else {
+- /* try to avoid compressing, so we can quickly seek backwards */
+- if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size))
+- return -2; /* buffer full */
+- }
++ for (;;) {
++ high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
++ if (zstream->eof_offset == high_offset) {
++ stream->istream.eof = TRUE;
++ return -1;
++ }
+
+- if (i_stream_read_more(stream->parent, &data, &size) < 0) {
+- if (stream->parent->stream_errno != 0) {
+- stream->istream.stream_errno =
+- stream->parent->stream_errno;
++ if (!zstream->marked) {
++ if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size))
++ return -2; /* buffer full */
+ } else {
+- i_assert(stream->parent->eof);
+- bzlib_read_error(zstream, "unexpected EOF");
+- if (!zstream->hdr_read)
+- stream->istream.stream_errno = EINVAL;
+- else
+- stream->istream.stream_errno = EPIPE;
++ /* try to avoid compressing, so we can quickly seek backwards */
++ if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size))
++ return -2; /* buffer full */
++ }
++
++ if (i_stream_read_more(stream->parent, &data, &size) < 0) {
++ if (stream->parent->stream_errno != 0) {
++ stream->istream.stream_errno =
++ stream->parent->stream_errno;
++ } else {
++ i_assert(stream->parent->eof);
++ bzlib_read_error(zstream, "unexpected EOF");
++ if (!zstream->hdr_read)
++ stream->istream.stream_errno = EINVAL;
++ else
++ stream->istream.stream_errno = EPIPE;
++ }
++ return -1;
++ }
++ if (size == 0) {
++ /* no more input */
++ i_assert(!stream->istream.blocking);
++ return 0;
+ }
+- return -1;
+- }
+- if (size == 0) {
+- /* no more input */
+- i_assert(!stream->istream.blocking);
+- return 0;
+- }
+
+- zstream->zs.next_in = (char *)data;
+- zstream->zs.avail_in = size;
++ zstream->zs.next_in = (char *)data;
++ zstream->zs.avail_in = size;
+
+- zstream->zs.next_out = (char *)stream->w_buffer + stream->pos;
+- zstream->zs.avail_out = out_size;
+- ret = BZ2_bzDecompress(&zstream->zs);
+- zstream->hdr_read = TRUE;
+-
+- out_size -= zstream->zs.avail_out;
+- stream->pos += out_size;
+-
+- i_stream_skip(stream->parent, size - zstream->zs.avail_in);
+-
+- switch (ret) {
+- case BZ_OK:
+- break;
+- case BZ_PARAM_ERROR:
+- i_unreached();
+- case BZ_DATA_ERROR:
+- bzlib_read_error(zstream, "corrupted data");
+- stream->istream.stream_errno = EINVAL;
+- return -1;
+- case BZ_DATA_ERROR_MAGIC:
+- bzlib_read_error(zstream,
+- "wrong magic in header (not bz2 file?)");
+- stream->istream.stream_errno = EINVAL;
+- return -1;
+- case BZ_MEM_ERROR:
+- i_fatal_status(FATAL_OUTOFMEM, "bzlib.read(%s): Out of memory",
+- i_stream_get_name(&stream->istream));
+- case BZ_STREAM_END:
+- zstream->eof_offset = stream->istream.v_offset +
+- (stream->pos - stream->skip);
+- stream->cached_stream_size = zstream->eof_offset;
+- if (out_size == 0) {
+- stream->istream.eof = TRUE;
++ zstream->zs.next_out = (char *)stream->w_buffer + stream->pos;
++ zstream->zs.avail_out = out_size;
++ ret = BZ2_bzDecompress(&zstream->zs);
++ zstream->hdr_read = TRUE;
++
++ out_size -= zstream->zs.avail_out;
++ stream->pos += out_size;
++
++ i_stream_skip(stream->parent, size - zstream->zs.avail_in);
++
++ switch (ret) {
++ case BZ_OK:
++ break;
++ case BZ_PARAM_ERROR:
++ i_unreached();
++ case BZ_DATA_ERROR:
++ bzlib_read_error(zstream, "corrupted data");
++ stream->istream.stream_errno = EINVAL;
+ return -1;
++ case BZ_DATA_ERROR_MAGIC:
++ bzlib_read_error(zstream,
++ "wrong magic in header (not bz2 file?)");
++ stream->istream.stream_errno = EINVAL;
++ return -1;
++ case BZ_MEM_ERROR:
++ i_fatal_status(FATAL_OUTOFMEM, "bzlib.read(%s): Out of memory",
++ i_stream_get_name(&stream->istream));
++ case BZ_STREAM_END:
++ zstream->eof_offset = stream->istream.v_offset +
++ (stream->pos - stream->skip);
++ stream->cached_stream_size = zstream->eof_offset;
++ if (out_size == 0) {
++ stream->istream.eof = TRUE;
++ return -1;
++ }
++ break;
++ default:
++ i_fatal("BZ2_bzDecompress() failed with %d", ret);
+ }
+- break;
+- default:
+- i_fatal("BZ2_bzDecompress() failed with %d", ret);
+- }
+- if (out_size == 0) {
+- /* read more input */
+- return i_stream_bzlib_read(stream);
++ if (out_size == 0) {
++ /* read more input */
++ return i_stream_bzlib_read(stream);
++ }
++ return out_size;
+ }
+- return out_size;
+ }
+
+ static void i_stream_bzlib_init(struct bzlib_istream *zstream)
+Index: trixie/src/lib-compression/istream-lz4.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-lz4.c
++++ trixie/src/lib-compression/istream-lz4.c
+@@ -149,67 +149,69 @@ static ssize_t i_stream_lz4_read(struct
+ zstream->header_read = TRUE;
+ }
+
+- if (zstream->chunk_left == 0) {
+- while ((ret = i_stream_lz4_read_chunk_header(zstream)) == 0) {
+- if (!stream->istream.blocking)
+- return 0;
++ for (;;) {
++ if (zstream->chunk_left == 0) {
++ while ((ret = i_stream_lz4_read_chunk_header(zstream)) == 0) {
++ if (!stream->istream.blocking)
++ return 0;
++ }
++ if (ret < 0)
++ return ret;
+ }
+- if (ret < 0)
+- return ret;
+- }
+
+- /* read the whole compressed chunk into memory */
+- while (zstream->chunk_left > 0 &&
+- (ret = i_stream_read_more(zstream->istream.parent, &data, &size)) > 0) {
+- if (size > zstream->chunk_left)
+- size = zstream->chunk_left;
+- buffer_append(zstream->chunk_buf, data, size);
+- i_stream_skip(zstream->istream.parent, size);
+- zstream->chunk_left -= size;
+- }
+- if (zstream->chunk_left > 0) {
+- if (ret == -1 && zstream->istream.parent->stream_errno == 0) {
+- lz4_read_error(zstream, "truncated lz4 chunk");
+- stream->istream.stream_errno = EPIPE;
+- return -1;
++ /* read the whole compressed chunk into memory */
++ while (zstream->chunk_left > 0 &&
++ (ret = i_stream_read_more(zstream->istream.parent, &data, &size)) > 0) {
++ if (size > zstream->chunk_left)
++ size = zstream->chunk_left;
++ buffer_append(zstream->chunk_buf, data, size);
++ i_stream_skip(zstream->istream.parent, size);
++ zstream->chunk_left -= size;
+ }
+- zstream->istream.istream.stream_errno =
+- zstream->istream.parent->stream_errno;
+- i_assert(ret != 0 || !stream->istream.blocking);
+- return ret;
+- }
+- /* if we already have max_buffer_size amount of data, fail here */
+- if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream))
+- return -2;
+- if (i_stream_get_data_size(zstream->istream.parent) > 0) {
+- /* Parent stream was only partially consumed. Set the stream's
+- IO as pending to avoid hangs. */
+- i_stream_set_input_pending(&zstream->istream.istream, TRUE);
+- }
+- /* allocate enough space for the old data and the new
+- decompressed chunk. we don't know the original compressed size,
+- so just allocate the max amount of memory. */
+- void *dest = i_stream_alloc(stream, zstream->max_uncompressed_chunk_size);
+- ret = LZ4_decompress_safe(zstream->chunk_buf->data, dest,
+- zstream->chunk_buf->used,
+- zstream->max_uncompressed_chunk_size);
+- i_assert(ret <= (int)zstream->max_uncompressed_chunk_size);
+- if (ret < 0) {
+- lz4_read_error(zstream, "corrupted lz4 chunk");
+- stream->istream.stream_errno = EINVAL;
+- return -1;
+- } else if (ret == 0)
+- return i_stream_lz4_read(stream);
+- i_assert(ret > 0);
+- stream->pos += ret;
+- i_assert(stream->pos <= stream->buffer_size);
++ if (zstream->chunk_left > 0) {
++ if (ret == -1 && zstream->istream.parent->stream_errno == 0) {
++ lz4_read_error(zstream, "truncated lz4 chunk");
++ stream->istream.stream_errno = EPIPE;
++ return -1;
++ }
++ zstream->istream.istream.stream_errno =
++ zstream->istream.parent->stream_errno;
++ i_assert(ret != 0 || !stream->istream.blocking);
++ return ret;
++ }
++ /* if we already have max_buffer_size amount of data, fail here */
++ if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream))
++ return -2;
++ if (i_stream_get_data_size(zstream->istream.parent) > 0) {
++ /* Parent stream was only partially consumed. Set the stream's
++ IO as pending to avoid hangs. */
++ i_stream_set_input_pending(&zstream->istream.istream, TRUE);
++ }
++ /* allocate enough space for the old data and the new
++ decompressed chunk. we don't know the original compressed size,
++ so just allocate the max amount of memory. */
++ void *dest = i_stream_alloc(stream, zstream->max_uncompressed_chunk_size);
++ ret = LZ4_decompress_safe(zstream->chunk_buf->data, dest,
++ zstream->chunk_buf->used,
++ zstream->max_uncompressed_chunk_size);
++ i_assert(ret <= (int)zstream->max_uncompressed_chunk_size);
++ if (ret < 0) {
++ lz4_read_error(zstream, "corrupted lz4 chunk");
++ stream->istream.stream_errno = EINVAL;
++ return -1;
++ } else if (ret == 0)
++ return i_stream_lz4_read(stream);
++ i_assert(ret > 0);
++ stream->pos += ret;
++ i_assert(stream->pos <= stream->buffer_size);
+
+- /* we are going to get next chunk after this, so reset here
+- so we can reuse the chunk buf for reading next buffer prefix */
+- if (zstream->chunk_left == 0)
+- buffer_set_used_size(zstream->chunk_buf, 0);
++ /* we are going to get next chunk after this, so reset here
++ so we can reuse the chunk buf for reading next buffer prefix */
++ if (zstream->chunk_left == 0)
++ buffer_set_used_size(zstream->chunk_buf, 0);
+
+- return ret;
++ return ret;
++ }
+ }
+
+ static void i_stream_lz4_reset(struct lz4_istream *zstream)
+Index: trixie/src/lib-compression/istream-zlib.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-zlib.c
++++ trixie/src/lib-compression/istream-zlib.c
+@@ -168,155 +168,157 @@ static ssize_t i_stream_zlib_read(struct
+ size_t size, out_size;
+ int ret;
+
+- high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
+- if (zstream->eof_offset == high_offset) {
+- /* zlib library returned EOF. */
+- if (!zstream->gz) {
+- /* deflate - ignore if there's still more data */
+- stream->istream.eof = TRUE;
+- return -1;
++ for (;;) {
++ high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
++ if (zstream->eof_offset == high_offset) {
++ /* zlib library returned EOF. */
++ if (!zstream->gz) {
++ /* deflate - ignore if there's still more data */
++ stream->istream.eof = TRUE;
++ return -1;
++ }
++ /* gz format - read the trailer */
++ if (!zstream->trailer_read) {
++ do {
++ ret = i_stream_zlib_read_trailer(zstream);
++ } while (ret == 0 && stream->istream.blocking);
++ if (ret <= 0)
++ return ret;
++ }
++ /* See if there's another concatenated gz stream. */
++ if (i_stream_read_eof(stream->parent)) {
++ /* EOF or error */
++ stream->istream.stream_errno =
++ stream->parent->stream_errno;
++ stream->istream.eof = TRUE;
++ return -1;
++ }
++ /* Multiple gz streams concatenated together */
++ zstream->starting_concatenated_output = TRUE;
++ }
++ if (zstream->starting_concatenated_output) {
++ /* make sure there actually is something in parent stream.
++ we don't want to reset the stream unless we actually see
++ some concatenated output. */
++ ret = i_stream_read_more(stream->parent, &data, &size);
++ if (ret <= 0) {
++ if (ret == 0)
++ return 0;
++ if (stream->parent->stream_errno != 0) {
++ stream->istream.stream_errno =
++ stream->parent->stream_errno;
++ }
++ stream->istream.eof = TRUE;
++ return -1;
++ }
++
++ /* gzip file with concatenated content */
++ stream->cached_stream_size = UOFF_T_MAX;
++ zstream->eof_offset = UOFF_T_MAX;
++ zstream->header_read = FALSE;
++ zstream->trailer_read = FALSE;
++ zstream->crc32 = 0;
++ zstream->starting_concatenated_output = FALSE;
++
++ (void)inflateEnd(&zstream->zs);
++ i_stream_zlib_init(zstream);
+ }
+- /* gz format - read the trailer */
+- if (!zstream->trailer_read) {
++
++ if (!zstream->header_read) {
+ do {
+- ret = i_stream_zlib_read_trailer(zstream);
++ ret = i_stream_zlib_read_header(stream);
+ } while (ret == 0 && stream->istream.blocking);
+ if (ret <= 0)
+ return ret;
++ zstream->header_read = TRUE;
+ }
+- /* See if there's another concatenated gz stream. */
+- if (i_stream_read_eof(stream->parent)) {
+- /* EOF or error */
+- stream->istream.stream_errno =
+- stream->parent->stream_errno;
+- stream->istream.eof = TRUE;
+- return -1;
++
++ if (!zstream->marked) {
++ if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size))
++ return -2; /* buffer full */
++ } else {
++ /* try to avoid compressing, so we can quickly seek backwards */
++ if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size))
++ return -2; /* buffer full */
+ }
+- /* Multiple gz streams concatenated together */
+- zstream->starting_concatenated_output = TRUE;
+- }
+- if (zstream->starting_concatenated_output) {
+- /* make sure there actually is something in parent stream.
+- we don't want to reset the stream unless we actually see
+- some concatenated output. */
+- ret = i_stream_read_more(stream->parent, &data, &size);
+- if (ret <= 0) {
+- if (ret == 0)
+- return 0;
++
++ if (i_stream_read_more(stream->parent, &data, &size) < 0) {
+ if (stream->parent->stream_errno != 0) {
+ stream->istream.stream_errno =
+ stream->parent->stream_errno;
++ } else {
++ i_assert(stream->parent->eof);
++ zlib_read_error(zstream, "unexpected EOF");
++ stream->istream.stream_errno = EPIPE;
+ }
+- stream->istream.eof = TRUE;
+ return -1;
+ }
+-
+- /* gzip file with concatenated content */
+- stream->cached_stream_size = UOFF_T_MAX;
+- zstream->eof_offset = UOFF_T_MAX;
+- zstream->header_read = FALSE;
+- zstream->trailer_read = FALSE;
+- zstream->crc32 = 0;
+- zstream->starting_concatenated_output = FALSE;
+-
+- (void)inflateEnd(&zstream->zs);
+- i_stream_zlib_init(zstream);
+- }
+-
+- if (!zstream->header_read) {
+- do {
+- ret = i_stream_zlib_read_header(stream);
+- } while (ret == 0 && stream->istream.blocking);
+- if (ret <= 0)
+- return ret;
+- zstream->header_read = TRUE;
+- }
+-
+- if (!zstream->marked) {
+- if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size))
+- return -2; /* buffer full */
+- } else {
+- /* try to avoid compressing, so we can quickly seek backwards */
+- if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size))
+- return -2; /* buffer full */
+- }
+-
+- if (i_stream_read_more(stream->parent, &data, &size) < 0) {
+- if (stream->parent->stream_errno != 0) {
+- stream->istream.stream_errno =
+- stream->parent->stream_errno;
+- } else {
+- i_assert(stream->parent->eof);
+- zlib_read_error(zstream, "unexpected EOF");
+- stream->istream.stream_errno = EPIPE;
+- }
+- return -1;
+- }
+- if (size == 0) {
+- /* no more input */
+- i_assert(!stream->istream.blocking);
+- return 0;
+- }
+-
+- zstream->zs.next_in = (void *)data;
+- zstream->zs.avail_in = size;
+-
+- zstream->zs.next_out = stream->w_buffer + stream->pos;
+- zstream->zs.avail_out = out_size;
+- ret = inflate(&zstream->zs, Z_SYNC_FLUSH);
+-
+- out_size -= zstream->zs.avail_out;
+- /* CRC32 is only needed for GZ trailer. */
+- if (zstream->gz)
+- zstream->crc32 = crc32_data_more(zstream->crc32,
+- stream->w_buffer + stream->pos,
+- out_size);
+- stream->pos += out_size;
+-
+- size_t bytes_consumed = size - zstream->zs.avail_in;
+- i_stream_skip(stream->parent, bytes_consumed);
+- if (i_stream_get_data_size(stream->parent) > 0 &&
+- (bytes_consumed > 0 || out_size > 0)) {
+- /* Parent stream was only partially consumed. Set the stream's
+- IO as pending to avoid hangs. */
+- i_stream_set_input_pending(&stream->istream, TRUE);
+- }
+-
+- switch (ret) {
+- case Z_OK:
+- break;
+- case Z_NEED_DICT:
+- zlib_read_error(zstream, "can't read file without dict");
+- stream->istream.stream_errno = EIO;
+- return -1;
+- case Z_DATA_ERROR:
+- zlib_read_error(zstream, "corrupted data");
+- stream->istream.stream_errno = EINVAL;
+- return -1;
+- case Z_MEM_ERROR:
+- i_fatal_status(FATAL_OUTOFMEM, "zlib.read(%s): Out of memory",
+- i_stream_get_name(&stream->istream));
+- case Z_STREAM_END:
+- zstream->eof_offset = stream->istream.v_offset +
+- (stream->pos - stream->skip);
+- stream->cached_stream_size = zstream->eof_offset;
+- zstream->zs.avail_in = 0;
+-
+- if (!zstream->trailer_read) {
+- /* try to read and verify the trailer, we might not
+- be called again. */
+- if (i_stream_zlib_read_trailer(zstream) < 0)
+- return -1;
++ if (size == 0) {
++ /* no more input */
++ i_assert(!stream->istream.blocking);
++ return 0;
++ }
++
++ zstream->zs.next_in = (void *)data;
++ zstream->zs.avail_in = size;
++
++ zstream->zs.next_out = stream->w_buffer + stream->pos;
++ zstream->zs.avail_out = out_size;
++ ret = inflate(&zstream->zs, Z_SYNC_FLUSH);
++
++ out_size -= zstream->zs.avail_out;
++ /* CRC32 is only needed for GZ trailer. */
++ if (zstream->gz)
++ zstream->crc32 = crc32_data_more(zstream->crc32,
++ stream->w_buffer + stream->pos,
++ out_size);
++ stream->pos += out_size;
++
++ size_t bytes_consumed = size - zstream->zs.avail_in;
++ i_stream_skip(stream->parent, bytes_consumed);
++ if (i_stream_get_data_size(stream->parent) > 0 &&
++ (bytes_consumed > 0 || out_size > 0)) {
++ /* Parent stream was only partially consumed. Set the stream's
++ IO as pending to avoid hangs. */
++ i_stream_set_input_pending(&stream->istream, TRUE);
++ }
++
++ switch (ret) {
++ case Z_OK:
++ break;
++ case Z_NEED_DICT:
++ zlib_read_error(zstream, "can't read file without dict");
++ stream->istream.stream_errno = EIO;
++ return -1;
++ case Z_DATA_ERROR:
++ zlib_read_error(zstream, "corrupted data");
++ stream->istream.stream_errno = EINVAL;
++ return -1;
++ case Z_MEM_ERROR:
++ i_fatal_status(FATAL_OUTOFMEM, "zlib.read(%s): Out of memory",
++ i_stream_get_name(&stream->istream));
++ case Z_STREAM_END:
++ zstream->eof_offset = stream->istream.v_offset +
++ (stream->pos - stream->skip);
++ stream->cached_stream_size = zstream->eof_offset;
++ zstream->zs.avail_in = 0;
++
++ if (!zstream->trailer_read) {
++ /* try to read and verify the trailer, we might not
++ be called again. */
++ if (i_stream_zlib_read_trailer(zstream) < 0)
++ return -1;
++ }
++ break;
++ default:
++ i_fatal("inflate() failed with %d", ret);
++ }
++ if (out_size == 0) {
++ /* read more input */
++ return i_stream_zlib_read(stream);
+ }
+- break;
+- default:
+- i_fatal("inflate() failed with %d", ret);
+- }
+- if (out_size == 0) {
+- /* read more input */
+- return i_stream_zlib_read(stream);
++ return out_size;
+ }
+- return out_size;
+ }
+
+ static void i_stream_zlib_init(struct zlib_istream *zstream)
--- /dev/null
+From 5e72915a0d803154ab50958364ff4ce78ea146da Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 3 Jun 2026 18:04:42 +0300
+Subject: [PATCH 1/5] lib: i_close_fd*() - Document that it preserves errno
+
+---
+ src/lib/fd-util.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/lib/fd-util.h b/src/lib/fd-util.h
+index 54bdd63160..3f46892759 100644
+--- a/src/lib/fd-util.h
++++ b/src/lib/fd-util.h
+@@ -17,7 +17,7 @@ void fd_close_maybe_stdio(int *fd_in, int *fd_out);
+
+ /* Close the fd and set it to -1. This assert-crashes if fd == 0, and is a
+ no-op if fd == -1. Normally fd == 0 would happen only if an uninitialized
+- fd is attempted to be closed, which is a bug. */
++ fd is attempted to be closed, which is a bug. Preserves original errno. */
+ void i_close_fd_path(int *fd, const char *path, const char *arg,
+ const char *func, const char *file, int line);
+ #define i_close_fd_path(fd, path) i_close_fd_path((fd), (path), #fd, __func__, __FILE__, __LINE__)
+--
+2.39.5
+
--- /dev/null
+From 16ff02fd5635a3b6583a276377a8b6880047d601 Mon Sep 17 00:00:00 2001
+From: Noah Meyerhans <noahm@debian.org>
+Date: Thu, 10 Sep 2026 09:59:18 -0400
+Subject: [PATCH] lib-imap: imap-match - Fix excessive CPU usage caused by
+ backtracking
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Backport the NFA/non-backtracking algorithm from 2684624… while retaining the
+byte-oriented matcher semantics of 2.4.1.
+
+Original commit message:
+
+Replace the recursive backtracking matcher with a Thompson-style NFA
+simulation over grapheme clusters.
+
+Each grapheme cluster of the compressed pattern becomes one state:
+LITERAL, PERCENT (consume any number of non-separator clusters) or STAR
+(consume any number of clusters including separators). A virtual
+ACCEPT position sits at index n_states. Simulation tracks the set of
+active positions in a bitmap. Epsilon-closure (skipping a PERCENT or
+STAR without consuming) is a single forward pass because the NFA is
+linear: each state can only epsilon-skip to i+1.
+
+The resulting match is O(n_data * n_pattern) regardless of pattern
+shape, with no recursion and no backtracking, so there is no way for
+a malicious pattern or mailbox name to trigger exponential CPU,
+excessive stack depth, or unbounded memory.
+
+IMAP_MATCH_YES / NO / CHILDREN / PARENT semantics are preserved:
+
+- YES: ACCEPT reachable after consuming all data.
+- PARENT: ACCEPT was active at some point while the next data
+ grapheme cluster was the separator.
+- CHILDREN: some active non-ACCEPT state remains after consuming all
+ data, and either the data ends with a separator or an
+ active state can still consume a separator (precomputed
+ as sep_accept[]).
+
+Inboxcase handling (case-insensitive comparison for the INBOX prefix
+of data) and grapheme-cluster comparison are unchanged - the existing
+match_gc logic is reused inline as literal_matches().
+pattern_compress() and pattern_is_inboxcase() are unchanged.
+---
+ src/lib-imap/imap-match.c | 307 ++++++++++++++++++++-------------
+ src/lib-imap/test-imap-match.c | 52 ++++++
+ 2 files changed, 239 insertions(+), 120 deletions(-)
+
+diff --git a/src/lib-imap/imap-match.c b/src/lib-imap/imap-match.c
+index 8603e004fb..fec5a3c242 100644
+--- a/src/lib-imap/imap-match.c
++++ b/src/lib-imap/imap-match.c
+@@ -9,9 +9,44 @@
+
+ #include <ctype.h>
+
++/* Pattern matching is implemented as a Thompson-style NFA simulation.
++ Each byte in the compressed pattern becomes one NFA state:
++
++ LITERAL - must consume a matching byte, with inboxcase fallback
++ PERCENT - may consume any number of non-separator bytes
++ STAR - may consume any number of bytes, including separators
++
++ A virtual ACCEPT position sits at index n_states. Simulation tracks the
++ set of active positions in boolean arrays. Epsilon transitions (skipping
++ a PERCENT or STAR without consuming) are applied as a single forward pass,
++ because the NFA is linear: each state can only epsilon-skip to i+1.
++
++ Complexity is O(n_data * n_pattern), regardless of wildcard count or
++ pattern shape, so there is no recursive backtracking and no way for a
++ malicious pattern or mailbox name to trigger exponential CPU usage.
++
++ This 2.4.1 backport keeps the historical byte-oriented matching model.
++ The upstream fix operates on grapheme clusters, but backporting that would
++ require additional Unicode matching changes outside the minimal ReDoS fix. */
++
++enum imap_match_nfa_type {
++ IMAP_MATCH_NFA_LITERAL = 0,
++ IMAP_MATCH_NFA_PERCENT,
++ IMAP_MATCH_NFA_STAR
++};
++
++struct imap_match_nfa_state {
++ enum imap_match_nfa_type type;
++ bool sep_accept;
++ unsigned char ch;
++};
++
+ struct imap_match_pattern {
+ const char *pattern;
+ bool inboxcase;
++
++ unsigned int n_states;
++ struct imap_match_nfa_state *states;
+ };
+
+ struct imap_match_glob {
+@@ -23,13 +58,6 @@ struct imap_match_glob {
+ char patterns_data[FLEXIBLE_ARRAY_MEMBER];
+ };
+
+-struct imap_match_context {
+- const char *inboxcase_end;
+-
+- char sep;
+- bool inboxcase;
+-};
+-
+ /* name of "INBOX" - must not have repeated substrings */
+ static const char inbox[] = "INBOX";
+ #define INBOXLEN (sizeof(inbox) - 1)
+@@ -108,6 +136,48 @@ static bool pattern_is_inboxcase(const char *pattern, char separator)
+ return TRUE;
+ }
+
++static void
++imap_match_compile(pool_t pool, struct imap_match_pattern *pat, char sep)
++{
++ unsigned int i;
++
++ pat->n_states = strlen(pat->pattern);
++ pat->states = pat->n_states == 0 ? NULL :
++ p_new(pool, struct imap_match_nfa_state, pat->n_states);
++
++ for (i = 0; i < pat->n_states; i++) {
++ struct imap_match_nfa_state *state = &pat->states[i];
++ unsigned char ch = (unsigned char)pat->pattern[i];
++
++ if (ch == '%')
++ state->type = IMAP_MATCH_NFA_PERCENT;
++ else if (ch == '*')
++ state->type = IMAP_MATCH_NFA_STAR;
++ else {
++ state->type = IMAP_MATCH_NFA_LITERAL;
++ state->ch = ch;
++ }
++ }
++
++ for (i = pat->n_states; i > 0; i--) {
++ unsigned int idx = i - 1;
++ const struct imap_match_nfa_state *state = &pat->states[idx];
++ bool consume_sep =
++ state->type == IMAP_MATCH_NFA_STAR ||
++ (state->type == IMAP_MATCH_NFA_LITERAL &&
++ state->ch == (unsigned char)sep);
++ bool eps_skippable =
++ state->type == IMAP_MATCH_NFA_PERCENT ||
++ state->type == IMAP_MATCH_NFA_STAR;
++ bool next_sep_accept =
++ idx + 1 < pat->n_states ?
++ pat->states[idx + 1].sep_accept : FALSE;
++
++ pat->states[idx].sep_accept =
++ consume_sep || (eps_skippable && next_sep_accept);
++ }
++}
++
+ static struct imap_match_glob *
+ imap_match_init_multiple_real(pool_t pool, const char *const *patterns,
+ bool inboxcase, char separator)
+@@ -138,7 +208,7 @@ imap_match_init_multiple_real(pool_t pool, const char *const *patterns,
+ glob->pool = pool;
+ glob->sep = separator;
+
+- /* copy pattern strings to our allocated memory */
++ /* copy pattern strings to our allocated memory and compile NFAs */
+ for (i = 0, pos = 0; i < patterns_count; i++) {
+ len = strlen(match_patterns[i].pattern) + 1;
+ i_assert(pos + len <= patterns_data_len);
+@@ -148,6 +218,8 @@ imap_match_init_multiple_real(pool_t pool, const char *const *patterns,
+ match_patterns[i].pattern, len);
+ match_patterns[i].pattern = glob->patterns_data + pos;
+ pos += len;
++
++ imap_match_compile(pool, &match_patterns[i], separator);
+ }
+ glob->patterns = match_patterns;
+ return glob;
+@@ -172,8 +244,15 @@ imap_match_init_multiple(pool_t pool, const char *const *patterns,
+
+ void imap_match_deinit(struct imap_match_glob **glob)
+ {
++ struct imap_match_pattern *p;
++
+ if (glob == NULL || *glob == NULL)
+ return;
++
++ for (p = (*glob)->patterns; p->pattern != NULL; p++) {
++ if (p->states != NULL)
++ p_free((*glob)->pool, p->states);
++ }
+ p_free((*glob)->pool, (*glob)->patterns);
+ p_free((*glob)->pool, *glob);
+ *glob = NULL;
+@@ -230,148 +309,136 @@ bool imap_match_globs_equal(const struct imap_match_glob *glob1,
+ return p1->pattern == p2->pattern;
+ }
+
+-#define CMP_CUR_CHR(ctx, data, pattern) \
+- (*(data) == *(pattern) || \
+- (i_toupper(*(data)) == i_toupper(*(pattern)) && \
+- (data) < (ctx)->inboxcase_end))
++static bool
++literal_matches(const struct imap_match_nfa_state *state,
++ unsigned char data_ch, bool inboxcase_pos)
++{
++ if (state->ch == data_ch)
++ return TRUE;
++ return inboxcase_pos &&
++ i_toupper(data_ch) == i_toupper(state->ch);
++}
+
+-static enum imap_match_result
+-match_sub(struct imap_match_context *ctx, const char **data_p,
+- const char **pattern_p)
++static void
++nfa_eps_close(const struct imap_match_pattern *pat, bool *bits)
+ {
+- enum imap_match_result ret, match;
+ unsigned int i;
+- const char *data = *data_p, *pattern = *pattern_p;
+-
+- /* match all non-wildcards */
+- i = 0;
+- while (pattern[i] != '\0' && pattern[i] != '*' && pattern[i] != '%') {
+- if (!CMP_CUR_CHR(ctx, data+i, pattern+i)) {
+- if (data[i] != '\0')
+- return IMAP_MATCH_NO;
+- if (pattern[i] == ctx->sep)
+- return IMAP_MATCH_CHILDREN;
+- if (i > 0 && pattern[i-1] == ctx->sep) {
+- /* data="foo/" pattern = "foo/bar/%" */
+- return IMAP_MATCH_CHILDREN;
+- }
+- return IMAP_MATCH_NO;
+- }
+- i++;
+- }
+- data += i;
+- pattern += i;
+
+- if (*data == '\0' && *data_p != data && data[-1] == ctx->sep &&
+- *pattern != '\0') {
+- /* data="/" pattern="/%..." */
+- match = IMAP_MATCH_CHILDREN;
+- } else {
+- match = IMAP_MATCH_NO;
++ for (i = 0; i < pat->n_states; i++) {
++ if (!bits[i])
++ continue;
++ if (pat->states[i].type == IMAP_MATCH_NFA_PERCENT ||
++ pat->states[i].type == IMAP_MATCH_NFA_STAR)
++ bits[i + 1] = TRUE;
+ }
+- while (*pattern == '%') {
+- pattern++;
+-
+- if (*pattern == '\0') {
+- /* match, if this is the last hierarchy */
+- while (*data != '\0' && *data != ctx->sep)
+- data++;
+- break;
+- }
+-
+- /* skip over this hierarchy */
+- while (*data != '\0') {
+- if (CMP_CUR_CHR(ctx, data, pattern)) {
+- ret = match_sub(ctx, &data, &pattern);
+- if (ret == IMAP_MATCH_YES)
+- break;
+-
+- match |= ret;
+- }
+-
+- if (*data == ctx->sep)
+- break;
++}
+
+- data++;
+- }
++static enum imap_match_result
++imap_match_pattern_run(const struct imap_match_pattern *pat,
++ const char *data, char sep, bool inboxcase_pattern)
++{
++ const char *inboxcase_end = data;
++ unsigned int n_bits = pat->n_states + 1;
++ enum imap_match_result result = IMAP_MATCH_NO;
++ bool parent_flag = FALSE;
++ bool data_ends_with_sep = FALSE;
++ bool *cur, *next;
++ const unsigned char *p;
++
++ if (inboxcase_pattern &&
++ strncasecmp(data, inbox, INBOXLEN) == 0 &&
++ (data[INBOXLEN] == '\0' || data[INBOXLEN] == sep)) {
++ inboxcase_end += INBOXLEN;
+ }
+
+- if (*pattern != '*') {
+- if (*data == '\0' && *pattern != '\0') {
+- if (*pattern == ctx->sep)
+- match |= IMAP_MATCH_CHILDREN;
+- return match;
+- }
++ cur = t_new(bool, n_bits);
++ next = t_new(bool, n_bits);
++ memset(cur, 0, n_bits * sizeof(*cur));
++ memset(next, 0, n_bits * sizeof(*next));
+
+- if (*data != '\0') {
+- if (*pattern == '\0' && *data == ctx->sep)
+- match |= IMAP_MATCH_PARENT;
+- return match;
+- }
+- }
++ cur[0] = TRUE;
++ nfa_eps_close(pat, cur);
+
+- *data_p = data;
+- *pattern_p = pattern;
+- return IMAP_MATCH_YES;
+-}
++ for (p = (const unsigned char *)data; *p != '\0'; p++) {
++ unsigned int i;
++ unsigned char ch = *p;
++ bool ch_is_sep = ch == (unsigned char)sep;
++ bool inboxcase_pos = (const char *)p < inboxcase_end;
+
+-static enum imap_match_result
+-imap_match_pattern(struct imap_match_context *ctx,
+- const char *data, const char *pattern)
+-{
+- enum imap_match_result ret, match;
++ if (ch_is_sep && cur[pat->n_states])
++ parent_flag = TRUE;
+
+- ctx->inboxcase_end = data;
+- if (ctx->inboxcase && strncasecmp(data, inbox, INBOXLEN) == 0 &&
+- (data[INBOXLEN] == '\0' || data[INBOXLEN] == ctx->sep)) {
+- /* data begins with INBOX/, use case-insensitive comparison
+- for it */
+- ctx->inboxcase_end += INBOXLEN;
+- }
++ memset(next, 0, n_bits * sizeof(*next));
++ for (i = 0; i < pat->n_states; i++) {
++ const struct imap_match_nfa_state *state;
+
+- if (*pattern != '*') {
+- /* handle the pattern up to the first '*' */
+- ret = match_sub(ctx, &data, &pattern);
+- if (ret != IMAP_MATCH_YES || *pattern == '\0')
+- return ret;
+- }
++ if (!cur[i])
++ continue;
+
+- match = IMAP_MATCH_CHILDREN;
+- while (*pattern == '*') {
+- pattern++;
++ state = &pat->states[i];
++ switch (state->type) {
++ case IMAP_MATCH_NFA_LITERAL:
++ if (literal_matches(state, ch, inboxcase_pos))
++ next[i + 1] = TRUE;
++ break;
++ case IMAP_MATCH_NFA_PERCENT:
++ if (!ch_is_sep)
++ next[i] = TRUE;
++ break;
++ case IMAP_MATCH_NFA_STAR:
++ next[i] = TRUE;
++ break;
++ }
++ }
+
+- if (*pattern == '\0')
+- return IMAP_MATCH_YES;
++ {
++ bool *tmp = cur;
++ cur = next;
++ next = tmp;
++ }
++ nfa_eps_close(pat, cur);
++ data_ends_with_sep = ch_is_sep;
++ }
+
+- while (*data != '\0') {
+- if (CMP_CUR_CHR(ctx, data, pattern)) {
+- ret = match_sub(ctx, &data, &pattern);
+- if (ret == IMAP_MATCH_YES)
+- break;
+- match |= ret;
++ if (cur[pat->n_states])
++ result = IMAP_MATCH_YES;
++ else {
++ unsigned int i;
++ bool has_nonaccept = FALSE;
++ bool has_sep_accept = FALSE;
++
++ for (i = 0; i < pat->n_states; i++) {
++ if (!cur[i])
++ continue;
++ has_nonaccept = TRUE;
++ if (pat->states[i].sep_accept) {
++ has_sep_accept = TRUE;
++ break;
+ }
+-
+- data++;
+ }
++
++ if (has_nonaccept && (data_ends_with_sep || has_sep_accept))
++ result |= IMAP_MATCH_CHILDREN;
++ if (parent_flag)
++ result |= IMAP_MATCH_PARENT;
+ }
+
+- return *data == '\0' && *pattern == '\0' ?
+- IMAP_MATCH_YES : match;
++ return result;
+ }
+
+ enum imap_match_result
+ imap_match(struct imap_match_glob *glob, const char *data)
+ {
+- struct imap_match_context ctx;
+ unsigned int i;
+ enum imap_match_result ret, match;
+
+ match = IMAP_MATCH_NO;
+- ctx.sep = glob->sep;
+ for (i = 0; glob->patterns[i].pattern != NULL; i++) {
+- ctx.inboxcase = glob->patterns[i].inboxcase;
+-
+- ret = imap_match_pattern(&ctx, data, glob->patterns[i].pattern);
++ T_BEGIN {
++ ret = imap_match_pattern_run(&glob->patterns[i], data,
++ glob->sep,
++ glob->patterns[i].inboxcase);
++ } T_END;
+ if (ret == IMAP_MATCH_YES)
+ return IMAP_MATCH_YES;
+
+diff --git a/src/lib-imap/test-imap-match.c b/src/lib-imap/test-imap-match.c
+index df911dae50..88459ea9c3 100644
+--- a/src/lib-imap/test-imap-match.c
++++ b/src/lib-imap/test-imap-match.c
+@@ -2,6 +2,7 @@
+
+ #include "lib.h"
+ #include "imap-match.h"
++#include "str.h"
+ #include "test-common.h"
+
+ struct test_imap_match {
+@@ -88,6 +89,56 @@ static void test_imap_match(void)
+ test_end();
+ }
+
++static void test_imap_match_no_redos(void)
++{
++ struct imap_match_glob *glob;
++ pool_t pool;
++ const unsigned int wildcard_count = 1000;
++ string_t *mailbox_name, *pattern;
++ unsigned int i;
++
++ pool = pool_alloconly_create("imap match redos", 1024);
++ test_begin("imap match no redos");
++
++ mailbox_name = str_new(pool, 256);
++ for (i = 0; i < 255; i++)
++ str_append_c(mailbox_name, 'a');
++
++ pattern = str_new(pool, wildcard_count * 2 + 10);
++ for (i = 0; i < 255; i++)
++ str_append(pattern, "%a");
++ glob = imap_match_init(pool, str_c(pattern), FALSE, '/');
++ test_assert(imap_match(glob, str_c(mailbox_name)) == IMAP_MATCH_YES);
++
++ str_truncate(pattern, 0);
++ for (i = 0; i < wildcard_count; i++)
++ str_append(pattern, "%a");
++ glob = imap_match_init(pool, str_c(pattern), FALSE, '/');
++ test_assert(imap_match(glob, str_c(mailbox_name)) == IMAP_MATCH_NO);
++
++ str_append_c(pattern, 'b');
++ glob = imap_match_init(pool, str_c(pattern), FALSE, '/');
++ test_assert(imap_match(glob, str_c(mailbox_name)) == IMAP_MATCH_NO);
++
++ str_truncate(pattern, 0);
++ for (i = 0; i < wildcard_count; i++)
++ str_append(pattern, "*a");
++ str_append_c(pattern, 'b');
++ glob = imap_match_init(pool, str_c(pattern), FALSE, '/');
++ test_assert(imap_match(glob, str_c(mailbox_name)) == IMAP_MATCH_CHILDREN);
++ p_clear(pool);
++
++ glob = imap_match_init(pool, "%a%a%a%a%ab", FALSE, '/');
++ test_assert(imap_match(glob, "aaaaab") == IMAP_MATCH_YES);
++ p_clear(pool);
++
++ glob = imap_match_init(pool, "%a%a%a%a%ab", FALSE, '/');
++ test_assert(imap_match(glob, "aaaaa") == IMAP_MATCH_NO);
++
++ pool_unref(&pool);
++ test_end();
++}
++
+ static void test_imap_match_globs_equal(void)
+ {
+ struct imap_match_glob *glob;
+@@ -120,6 +171,7 @@ int main(void)
+ {
+ static void (*const test_functions[])(void) = {
+ test_imap_match,
++ test_imap_match_no_redos,
+ test_imap_match_globs_equal,
+ NULL
+ };
+--
+2.47.3
+
--- /dev/null
+From 366ca92f916953c2e475ff20dd7d21fcca5a1987 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 6 May 2026 14:53:41 +0000
+Subject: [PATCH] lib-imap-urlauth: Fix leaking uninitialized memory into
+ client error message
+
+imap_urlauth_fetch_parsed() called imap_msgpart_url_open_mailbox() with
+client_error_r and then, on the ret==0 (mailbox-not-found) branch,
+formatted a separate uninitialized local "error" pointer with
+t_strdup_printf("Invalid URLAUTH: %s", error). The %s read process
+stack memory until a NUL byte and sent it to the authenticated IMAP
+client inside the "* NO Failed to fetch URLAUTH ..." response.
+
+Broken by bb193c273e63ffa42c5c0b51ecd8860398e3beab
+---
+ src/lib-imap-urlauth/imap-urlauth.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/lib-imap-urlauth/imap-urlauth.c b/src/lib-imap-urlauth/imap-urlauth.c
+index 3cdcc21bb6..21f4520a93 100644
+--- a/src/lib-imap-urlauth/imap-urlauth.c
++++ b/src/lib-imap-urlauth/imap-urlauth.c
+@@ -513,7 +513,8 @@ int imap_urlauth_fetch_parsed(struct imap_urlauth_context *uctx,
+ }
+
+ if ((ret = imap_msgpart_url_open_mailbox(mpurl, &box, error_code_r,
+- client_error_r)) < 0) {
++ &error)) < 0) {
++ *client_error_r = t_strdup_printf("Invalid URLAUTH: %s", error);
+ imap_msgpart_url_free(&mpurl);
+ return -1;
+ }
+--
+2.47.3
+
--- /dev/null
+From 3a25b72230003ca7e133dc1117cc5e0a2f006ef3 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 11 Jun 2026 17:20:32 +0000
+Subject: [PATCH 1/2] lib-lda: mail-deliver - Autocreate delivery mailbox via
+ explicit mailbox_create()
+
+Instead of relying on MAILBOX_FLAG_AUTO_CREATE to have mailbox_open()
+implicitly create the target, open it and, on MAIL_ERROR_NOTFOUND,
+create it explicitly when lda_mailbox_autocreate is enabled. This makes
+the creation go through the normal mailbox_create() path (including ACL
+checks) and removes one of the two users of the autocreate mailbox flags.
+---
+ src/lib-lda/mail-deliver.c | 29 ++++++++++++++++++++++++-----
+ 1 file changed, 24 insertions(+), 5 deletions(-)
+
+diff --git a/src/lib-lda/mail-deliver.c b/src/lib-lda/mail-deliver.c
+index 1001fed76b..605353bb8d 100644
+--- a/src/lib-lda/mail-deliver.c
++++ b/src/lib-lda/mail-deliver.c
+@@ -244,16 +244,35 @@ int mail_deliver_save_open(struct mail_deliver_save_open_context *ctx,
+ return -1;
+ }
+
+- if (ctx->lda_mailbox_autocreate)
+- flags |= MAILBOX_FLAG_AUTO_CREATE;
+- if (ctx->lda_mailbox_autosubscribe)
+- flags |= MAILBOX_FLAG_AUTO_SUBSCRIBE;
+ *box_r = box = mailbox_alloc_for_user(ctx->user, name, flags);
+
+ if (mailbox_open(box) == 0)
+ return 0;
+ *error_str_r = mailbox_get_last_internal_error(box, error_r);
+- return -1;
++
++ /* Autocreate the mailbox if it does not exist yet and
++ lda_mailbox_autocreate is enabled. */
++ if (*error_r != MAIL_ERROR_NOTFOUND || !ctx->lda_mailbox_autocreate)
++ return -1;
++
++ if (mailbox_create(box, NULL, FALSE) < 0) {
++ enum mail_error error;
++
++ *error_str_r = mailbox_get_last_internal_error(box, &error);
++ if (error != MAIL_ERROR_EXISTS) {
++ *error_r = error;
++ return -1;
++ }
++ /* Someone else just created it; fall through and open it. */
++ } else if (ctx->lda_mailbox_autosubscribe) {
++ (void)mailbox_set_subscribed(box, TRUE);
++ }
++
++ if (mailbox_open(box) < 0) {
++ *error_str_r = mailbox_get_last_internal_error(box, error_r);
++ return -1;
++ }
++ return 0;
+ }
+
+ static bool mail_deliver_check_duplicate(struct mail_deliver_session *session,
+--
+2.39.5
+
--- /dev/null
+From 3d300ea1f26efd7f0207c5e3efd81f644ca03bf9 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Sat, 28 Feb 2026 10:27:19 +0200
+Subject: [PATCH 01/14] lib-mail: Reset charset translation buffer between MIME
+ parts
+
+If MIME part ended with an incomplete charset translation, the buffer was
+kept for the next MIME part. This could have produced garbage in the next
+MIME part, or a crash.
+
+Fixes:
+Panic: file message-decoder.c: line 232 (translation_buf_decode): assertion failed: (orig_size < CHARSET_MAX_PENDING_BUF_SIZE)
+---
+ src/lib-mail/message-decoder.c | 4 +++
+ src/lib-mail/test-message-decoder.c | 52 +++++++++++++++++++++++++++++
+ 2 files changed, 56 insertions(+)
+
+diff --git a/src/lib-mail/message-decoder.c b/src/lib-mail/message-decoder.c
+index 845b3d5e3a..4eea9994a2 100644
+--- a/src/lib-mail/message-decoder.c
++++ b/src/lib-mail/message-decoder.c
+@@ -257,6 +257,10 @@ message_decode_body_init_charset(struct message_decoder_context *ctx,
+ if (ctx->binary_input)
+ return;
+
++ /* If some input was left untranslated in the previous MIME part,
++ it needs to be discarded now so it won't affect the next part. */
++ ctx->translation_size = 0;
++
+ if (ctx->charset_trans != NULL && ctx->content_charset != NULL &&
+ strcasecmp(ctx->content_charset, ctx->charset_trans_charset) == 0) {
+ /* already have the correct translation selected */
+diff --git a/src/lib-mail/test-message-decoder.c b/src/lib-mail/test-message-decoder.c
+index edf9210cda..83c267187b 100644
+--- a/src/lib-mail/test-message-decoder.c
++++ b/src/lib-mail/test-message-decoder.c
+@@ -498,6 +498,57 @@ UNICODE_REPLACEMENT_CHAR_UTF8;
+ test_end();
+ }
+
++static void test_message_decoder_charset_mime_part_change(void)
++{
++ static const unsigned char test_message_input[] =
++"Content-Type: multipart/mixed; boundary=\"1\"\n"
++"MIME-Version: 1.0\n\n"
++"--1\n"
++"Content-Type: text/plain; charset=utf-8\n\n"
++"\xc3\n"
++"--1\n"
++"Content-Type: text/plain; charset=utf-8\n\n"
++"\xa4\n"
++"--1--\n";
++
++ static const char *test_message_output =
++ UNICODE_REPLACEMENT_CHAR_UTF8;
++
++ test_begin("message decoder charset - mime part change");
++
++ const struct message_parser_settings parser_set = { .flags = 0, };
++ struct message_parser_ctx *parser;
++ struct message_decoder_context *decoder;
++ struct message_part *parts;
++ struct message_block input, output;
++ struct istream *istream;
++ string_t *str_out = t_str_new(20);
++ int ret;
++
++ pool_t pool = pool_alloconly_create("message parser", 10240);
++ istream = test_istream_create_data(test_message_input,
++ sizeof(test_message_input)-1);
++ parser = message_parser_init(pool, istream, &parser_set);
++ decoder = message_decoder_init(NULL, 0);
++
++ while ((ret = message_parser_parse_next_block(parser, &input)) > 0) {
++ message_part_data_parse_from_header(pool, input.part, input.hdr);
++ if (message_decoder_decode_next_block(decoder, &input, &output) &&
++ output.hdr == NULL && output.size > 0)
++ str_append_data(str_out, output.data, output.size);
++ }
++
++ test_assert(ret == -1);
++ test_assert_strcmp(test_message_output, str_c(str_out));
++ message_decoder_deinit(&decoder);
++ message_parser_deinit(&parser, &parts);
++ test_assert(istream->stream_errno == 0);
++
++ i_stream_unref(&istream);
++ pool_unref(&pool);
++ test_end();
++}
++
+ int main(void)
+ {
+ static void (*const test_functions[])(void) = {
+@@ -507,6 +558,7 @@ int main(void)
+ test_message_decoder_content_transfer_encoding,
+ test_message_decoder_invalid_content_transfer_encoding,
+ test_message_decoder_charset,
++ test_message_decoder_charset_mime_part_change,
+ NULL
+ };
+ return test_run(test_functions);
+--
+2.47.3
+
--- /dev/null
+From fc74e9b79107879c9f6f714c2c8becb533ba07d6 Mon Sep 17 00:00:00 2001
+From: Marco Bettini <marco.bettini@open-xchange.com>
+Date: Fri, 10 Apr 2026 08:53:36 +0000
+Subject: [PATCH 1/4] lib-mail: o_stream_dot_sendv() - Use I_MIN()
+
+---
+ src/lib-mail/ostream-dot.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+Index: trixie/src/lib-mail/ostream-dot.c
+===================================================================
+--- trixie.orig/src/lib-mail/ostream-dot.c
++++ trixie/src/lib-mail/ostream-dot.c
+@@ -198,8 +198,7 @@ o_stream_dot_sendv(struct ostream_privat
+ if (max_bytes == 0)
+ break;
+ i_assert(p <= pend);
+- chunk = ((size_t)(p-data) >= max_bytes ?
+- max_bytes : (size_t)(p - data));
++ chunk = I_MIN((size_t)(p - data), max_bytes);
+ if (chunk > 0) {
+ iovn.iov_base = data;
+ iovn.iov_len = chunk;
--- /dev/null
+From e94a6c3cb984f9d1e5fe51714ea5015205d79fd1 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Mon, 26 Jan 2026 14:55:08 +0200
+Subject: [PATCH] lib-mail: ostream-dot - Optimize stream writing
+
+Use i_memcspn() to figure out how much we can skip.
+---
+ src/lib-mail/ostream-dot.c | 14 +++++++++++++-
+ 1 file changed, 13 insertions(+), 1 deletion(-)
+
+Index: trixie/src/lib-mail/ostream-dot.c
+===================================================================
+--- trixie.orig/src/lib-mail/ostream-dot.c
++++ trixie/src/lib-mail/ostream-dot.c
+@@ -108,9 +108,17 @@ o_stream_dot_sendv(struct ostream_privat
+ for (; p < pend && (size_t)(p-data)+2 < max_bytes; p++) {
+ char add = 0;
+
++ size = pend - p;
+ switch (dstream->state) {
+ /* none */
+- case STREAM_STATE_NONE:
++ case STREAM_STATE_NONE: {
++ size_t maxlen = I_MIN(size, max_bytes - ((size_t)(p - data) + 2));
++ p = CONST_PTR_OFFSET(p, i_memcspn(p, maxlen, "\r\n", 2));
++ i_assert(p <= pend);
++ if (p == pend) {
++ p--;
++ continue;
++ }
+ switch (*p) {
+ case '\n':
+ dstream->state = STREAM_STATE_CRLF;
+@@ -122,8 +130,10 @@ o_stream_dot_sendv(struct ostream_privat
+ break;
+ }
+ break;
++ }
+ /* got CR */
+ case STREAM_STATE_CR:
++ i_assert(p < pend);
+ switch (*p) {
+ case '\r':
+ break;
+@@ -138,6 +148,7 @@ o_stream_dot_sendv(struct ostream_privat
+ /* got CRLF, or the first line */
+ case STREAM_STATE_INIT:
+ case STREAM_STATE_CRLF:
++ i_assert(p < pend);
+ switch (*p) {
+ case '\r':
+ dstream->state = STREAM_STATE_CR;
+@@ -186,6 +197,7 @@ o_stream_dot_sendv(struct ostream_privat
+
+ if (max_bytes == 0)
+ break;
++ i_assert(p <= pend);
+ chunk = ((size_t)(p-data) >= max_bytes ?
+ max_bytes : (size_t)(p - data));
+ if (chunk > 0) {
--- /dev/null
+From b949a172750084895a9b6e8317787a2229f6a8f0 Mon Sep 17 00:00:00 2001
+From: Stephan Bosch <stephan.bosch@open-xchange.com>
+Date: Sat, 11 Apr 2026 14:12:08 +0200
+Subject: [PATCH 1/2] lib-managesieve: managesieve-parser - Fix handling of
+ lone CR character
+
+---
+ src/lib-managesieve/managesieve-parser.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+Index: trixie/pigeonhole/src/lib-managesieve/managesieve-parser.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-managesieve/managesieve-parser.c
++++ trixie/pigeonhole/src/lib-managesieve/managesieve-parser.c
+@@ -441,6 +441,15 @@ static bool managesieve_parser_read_arg(
+
+ switch (data[0]) {
+ case '\r':
++ if (data_size == 1) {
++ /* Wait for LF */
++ return FALSE;
++ }
++ if (data[1] != '\n') {
++ parser->error = "CR sent without LF";
++ return FALSE;
++ }
++ /* Fall through */
+ case '\n':
+ /* Unexpected end of line */
+ parser->eol = TRUE;
--- /dev/null
+From da28fa5a900dc521a7f6cadf9320650dcad3cf18 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 5 May 2026 16:18:34 +0000
+Subject: [PATCH 1/7] lib-sieve: Add per-user sieve-rusage file for cumulative
+ CPU tracking
+
+Cumulative resource usage was previously stored in each compiled Sieve
+binary's (.svbin) header. Because the binary file is keyed by script
+name, a user could reset their CPU budget by uploading the same script
+under a different name (PUTSCRIPT newname + SETACTIVE newname),
+RENAMESCRIPT, or DELETESCRIPT followed by PUTSCRIPT under another
+name. Each such operation produced a fresh .svbin with zero counter,
+allowing the sieve_max_cpu_time limit to be bypassed indefinitely.
+
+Track cumulative CPU per user instead, in <INBOX-namespace-root>/sieve-rusage.
+The file persists across all script renames, deletions, and re-uploads,
+since it is not tied to any particular script name. Tracking is enabled
+only for personal storages where the user's INBOX namespace has a
+filesystem path; admin/default storages are unaffected here.
+
+The on-disk format is a single ASCII line:
+
+ V1 <flags> <cpu-secs> <update-time>
+
+CPU time is rounded up to whole seconds when persisted; sub-second
+precision is preserved only for the in-memory accumulation within a
+single execution. The file is updated by writing a new copy via
+file_dotlock_open_mode() and replacing the original via
+file_dotlock_replace(). Concurrent writers are serialized through the
+dotlock; readers do not need a lock because the rename is atomic.
+
+The .svbin header still carries its original resource_usage struct as
+the active fallback when no per-user file is available; a follow-up
+commit drops that path.
+---
+ src/lib-sieve/Makefile.am | 2 +
+ src/lib-sieve/sieve-binary-file.c | 44 +++++
+ src/lib-sieve/sieve-binary.c | 35 ++++
+ src/lib-sieve/sieve-binary.h | 5 +
+ src/lib-sieve/sieve-rusage.c | 266 ++++++++++++++++++++++++++
+ src/lib-sieve/sieve-rusage.h | 32 ++++
+ src/lib-sieve/sieve-storage-private.h | 11 ++
+ src/lib-sieve/sieve-storage.c | 2 +
+ src/lib-sieve/sieve.c | 5 +
+ 9 files changed, 402 insertions(+)
+ create mode 100644 src/lib-sieve/sieve-rusage.c
+ create mode 100644 src/lib-sieve/sieve-rusage.h
+
+Index: trixie/pigeonhole/src/lib-sieve/Makefile.am
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/Makefile.am
++++ trixie/pigeonhole/src/lib-sieve/Makefile.am
+@@ -109,6 +109,7 @@ libdovecot_sieve_la_SOURCES = \
+ sieve-ast.c \
+ sieve-binary.c \
+ sieve-binary-file.c \
++ sieve-rusage.c \
+ sieve-binary-code.c \
+ sieve-binary-debug.c \
+ sieve-parser.c \
+@@ -158,6 +159,7 @@ headers = \
+ sieve-ast.h \
+ sieve-binary.h \
+ sieve-binary-private.h \
++ sieve-rusage.h \
+ sieve-parser.h \
+ sieve-address.h \
+ sieve-validator.h \
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary-file.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary-file.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary-file.c
+@@ -18,6 +18,9 @@
+ #include "sieve-extensions.h"
+ #include "sieve-code.h"
+ #include "sieve-script.h"
++#include "sieve-script-private.h"
++#include "sieve-storage-private.h"
++#include "sieve-rusage.h"
+
+ #include "sieve-binary-private.h"
+
+@@ -1008,15 +1011,56 @@ sieve_binary_file_do_update_resource_usa
+ return ret;
+ }
+
++static int
++sieve_binary_storage_update_resource_usage(struct sieve_binary *sbin,
++ struct sieve_storage *storage,
++ enum sieve_error *error_code_r)
++{
++ struct sieve_resource_usage delta;
++ struct sieve_resource_usage total;
++ uint32_t flags;
++
++ delta = sbin->rusage;
++ sieve_binary_get_resource_usage(sbin, &total);
++ flags = sbin->header.flags & SIEVE_BINARY_FLAG_RESOURCE_LIMIT;
++
++ if (!HAS_ALL_BITS(flags, SIEVE_BINARY_FLAG_RESOURCE_LIMIT) &&
++ !sieve_resource_usage_is_high(sbin->svinst, &total)) {
++ /* Nothing meaningful to persist */
++ sieve_resource_usage_init(&sbin->rusage);
++ sbin->rusage_updated = FALSE;
++ return 0;
++ }
++
++ if (sieve_rusage_storage_add(storage, &delta, flags) < 0) {
++ *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
++ return -1;
++ }
++
++ sieve_resource_usage_init(&sbin->rusage);
++ sbin->rusage_updated = FALSE;
++ return 0;
++}
++
+ int sieve_binary_file_update_resource_usage(struct sieve_binary *sbin,
+ enum sieve_error *error_code_r)
+ {
++ struct sieve_storage *storage = NULL;
+ int fd, ret = 0;
+
+ sieve_error_args_init(&error_code_r, NULL);
+
+ sieve_binary_file_close(&sbin->file);
+
++ if (sbin->script != NULL && sbin->script->storage != NULL &&
++ sbin->script->storage->rusage_path != NULL)
++ storage = sbin->script->storage;
++
++ if (storage != NULL) {
++ return sieve_binary_storage_update_resource_usage(
++ sbin, storage, error_code_r);
++ }
++
+ if (sbin->path == NULL)
+ return 0;
+ if (sbin->header.version_major != SIEVE_BINARY_VERSION_MAJOR) {
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary.c
+@@ -9,6 +9,7 @@
+ #include "hash.h"
+ #include "array.h"
+ #include "ostream.h"
++#include "ioloop.h"
+ #include "eacces-error.h"
+ #include "safe-mkstemp.h"
+
+@@ -16,6 +17,9 @@
+ #include "sieve-extensions.h"
+ #include "sieve-code.h"
+ #include "sieve-script.h"
++#include "sieve-script-private.h"
++#include "sieve-storage-private.h"
++#include "sieve-rusage.h"
+
+ #include "sieve-binary-private.h"
+
+@@ -190,6 +194,37 @@ void sieve_binary_close(struct sieve_bin
+ * Resource usage
+ */
+
++static struct sieve_storage *
++sieve_binary_get_user_storage(struct sieve_binary *sbin)
++{
++ if (sbin->script == NULL)
++ return NULL;
++ if (sbin->script->storage == NULL)
++ return NULL;
++ if (sbin->script->storage->rusage_path == NULL)
++ return NULL;
++ return sbin->script->storage;
++}
++
++void sieve_binary_apply_persisted_rusage(struct sieve_binary *sbin)
++{
++ struct sieve_storage *storage = sieve_binary_get_user_storage(sbin);
++ struct sieve_resource_usage rusage;
++ struct sieve_binary_header *header = &sbin->header;
++ uint32_t flags = 0;
++
++ if (storage == NULL)
++ return;
++ if (sieve_rusage_storage_load(storage, &rusage, &flags) <= 0)
++ return;
++
++ header->resource_usage.cpu_time_msecs = rusage.cpu_time_msecs;
++ header->resource_usage.update_time = ioloop_time;
++ header->flags |= (flags & SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
++ sieve_resource_usage_init(&sbin->rusage);
++ sbin->rusage_updated = FALSE;
++}
++
+ void sieve_binary_get_resource_usage(struct sieve_binary *sbin,
+ struct sieve_resource_usage *rusage_r)
+ {
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary.h
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary.h
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary.h
+@@ -37,6 +37,11 @@ bool sieve_binary_record_resource_usage(
+ ATTR_NULL(1);
+ void sieve_binary_set_resource_usage(struct sieve_binary *sbin,
+ const struct sieve_resource_usage *rusage);
++
++/* Apply persisted per-user rusage and limit flag from sieve-rusage file
++ into the binary header. No-op when the storage has no per-user file. */
++void sieve_binary_apply_persisted_rusage(struct sieve_binary *sbin);
++
+ /*
+ * Accessors
+ */
+Index: trixie/pigeonhole/src/lib-sieve/sieve-rusage.c
+===================================================================
+--- /dev/null
++++ trixie/pigeonhole/src/lib-sieve/sieve-rusage.c
+@@ -0,0 +1,266 @@
++/* Copyright (c) 2026 Pigeonhole authors, see the included COPYING file
++ */
++
++#include "lib.h"
++#include "str.h"
++#include "strnum.h"
++#include "ioloop.h"
++#include "eacces-error.h"
++#include "file-dotlock.h"
++#include "mail-namespace.h"
++#include "mail-storage.h"
++#include "mailbox-list.h"
++
++#include "sieve-common.h"
++#include "sieve-settings.h"
++#include "sieve-storage-private.h"
++#include "sieve-rusage.h"
++
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <unistd.h>
++#include <fcntl.h>
++
++#define SIEVE_RUSAGE_FILENAME "sieve-rusage"
++#define SIEVE_RUSAGE_VERSION_TAG "V1"
++#define SIEVE_RUSAGE_LOCK_TIMEOUT 10
++#define SIEVE_RUSAGE_LOCK_STALE_TIMEOUT 60
++
++/* Maximum size of the on-disk file. Single-line ASCII format with four
++ space-separated tokens; anything larger is treated as corrupt. */
++#define SIEVE_RUSAGE_MAX_FILE_SIZE 128
++
++struct sieve_rusage_record {
++ uint32_t flags;
++ uint32_t cpu_time_msecs;
++ time_t update_time;
++};
++
++void sieve_rusage_storage_init(struct sieve_storage *storage,
++ struct mail_user *user)
++{
++ struct mail_namespace *ns;
++ const char *root;
++
++ if (!storage->is_personal || user == NULL)
++ return;
++
++ ns = mail_namespace_find_inbox(user->namespaces);
++ if (ns == NULL)
++ return;
++
++ if (!mailbox_list_get_root_path(ns->list, MAILBOX_LIST_PATH_TYPE_DIR,
++ &root) || root == NULL)
++ return;
++
++ storage->user = user;
++ storage->inbox_list = ns->list;
++ storage->rusage_path = p_strconcat(storage->pool, root, "/",
++ SIEVE_RUSAGE_FILENAME, NULL);
++}
++
++static bool
++sieve_rusage_parse(const char *line, struct sieve_rusage_record *rec)
++{
++ const char *const *tokens;
++ uint32_t cpu_secs;
++ bool ok = FALSE;
++
++ T_BEGIN {
++ tokens = t_strsplit_spaces(line, " \t");
++ if (tokens[0] == NULL ||
++ strcmp(tokens[0], SIEVE_RUSAGE_VERSION_TAG) != 0)
++ break;
++ if (tokens[1] == NULL || tokens[2] == NULL ||
++ tokens[3] == NULL || tokens[4] != NULL)
++ break;
++ if (str_to_uint32(tokens[1], &rec->flags) < 0)
++ break;
++ if (str_to_uint32(tokens[2], &cpu_secs) < 0)
++ break;
++ if (str_to_time(tokens[3], &rec->update_time) < 0)
++ break;
++ if (cpu_secs > UINT32_MAX / 1000)
++ rec->cpu_time_msecs = UINT32_MAX;
++ else
++ rec->cpu_time_msecs = cpu_secs * 1000;
++ ok = TRUE;
++ } T_END;
++
++ return ok;
++}
++
++static int
++sieve_rusage_read(struct sieve_storage *storage,
++ struct sieve_rusage_record *rec_r)
++{
++ char buf[SIEVE_RUSAGE_MAX_FILE_SIZE + 1];
++ char *nl;
++ ssize_t ret;
++ int fd;
++
++ i_zero(rec_r);
++
++ fd = open(storage->rusage_path, O_RDONLY);
++ if (fd < 0) {
++ if (errno == ENOENT)
++ return 0;
++ e_error(storage->event, "open(%s) failed: %m",
++ storage->rusage_path);
++ return -1;
++ }
++
++ ret = read(fd, buf, sizeof(buf) - 1);
++ i_close_fd(&fd);
++ if (ret < 0) {
++ e_error(storage->event, "read(%s) failed: %m",
++ storage->rusage_path);
++ return -1;
++ }
++ if (ret == 0)
++ return 0;
++ if (ret >= (ssize_t)(sizeof(buf) - 1)) {
++ e_warning(storage->event, "%s: file too large; resetting",
++ storage->rusage_path);
++ return 0;
++ }
++ buf[ret] = '\0';
++
++ nl = strchr(buf, '\n');
++ if (nl != NULL)
++ *nl = '\0';
++
++ if (!sieve_rusage_parse(buf, rec_r)) {
++ e_warning(storage->event, "%s: malformed content; resetting",
++ storage->rusage_path);
++ i_zero(rec_r);
++ }
++ return 0;
++}
++
++static void
++sieve_rusage_dotlock_settings(struct dotlock_settings *set_r)
++{
++ i_zero(set_r);
++ set_r->timeout = SIEVE_RUSAGE_LOCK_TIMEOUT;
++ set_r->stale_timeout = SIEVE_RUSAGE_LOCK_STALE_TIMEOUT;
++ set_r->use_excl_lock = TRUE;
++}
++
++int sieve_rusage_storage_load(struct sieve_storage *storage,
++ struct sieve_resource_usage *rusage_r,
++ uint32_t *flags_r)
++{
++ struct sieve_rusage_record rec;
++ unsigned int timeout;
++
++ sieve_resource_usage_init(rusage_r);
++ *flags_r = 0;
++
++ if (storage->rusage_path == NULL)
++ return 0;
++
++ if (sieve_rusage_read(storage, &rec) < 0)
++ return -1;
++
++ timeout = storage->svinst->set->resource_usage_timeout;
++ if (rec.update_time != 0 &&
++ (ioloop_time - rec.update_time) > (time_t)timeout) {
++ /* decayed */
++ return 1;
++ }
++
++ rusage_r->cpu_time_msecs = rec.cpu_time_msecs;
++ *flags_r = rec.flags;
++ return 1;
++}
++
++int sieve_rusage_storage_add(struct sieve_storage *storage,
++ const struct sieve_resource_usage *delta_rusage,
++ uint32_t flags_to_set)
++{
++ struct sieve_rusage_record rec;
++ struct dotlock_settings dlset;
++ struct dotlock *dotlock;
++ unsigned int timeout;
++ uint32_t old_cpu, cpu_secs;
++ string_t *out;
++ ssize_t wret;
++ int fd;
++
++ if (storage->rusage_path == NULL)
++ return 0;
++
++ sieve_rusage_dotlock_settings(&dlset);
++
++ /* The namespace root may not exist yet on the very first update for
++ a user (e.g. LDA refused execution before any mail was delivered).
++ Create it on demand so file_dotlock_open does not fail with ENOENT. */
++ if (storage->inbox_list != NULL &&
++ mailbox_list_mkdir_root(storage->inbox_list, NULL,
++ MAILBOX_LIST_PATH_TYPE_DIR) < 0)
++ return -1;
++
++ fd = file_dotlock_open_mode(&dlset, storage->rusage_path, 0,
++ 0600, (uid_t)-1, (gid_t)-1, &dotlock);
++ if (fd == -1) {
++ if (errno == EACCES) {
++ e_error(storage->event, "%s",
++ eacces_error_get_creating("file_dotlock_open",
++ storage->rusage_path));
++ } else {
++ e_error(storage->event,
++ "file_dotlock_open(%s) failed: %m",
++ storage->rusage_path);
++ }
++ return -1;
++ }
++
++ if (sieve_rusage_read(storage, &rec) < 0) {
++ file_dotlock_delete(&dotlock);
++ return -1;
++ }
++
++ timeout = storage->svinst->set->resource_usage_timeout;
++ if (rec.update_time != 0 &&
++ (ioloop_time - rec.update_time) > (time_t)timeout) {
++ /* decayed: discard previous values */
++ rec.cpu_time_msecs = 0;
++ rec.flags = 0;
++ }
++
++ old_cpu = rec.cpu_time_msecs;
++ if ((UINT32_MAX - old_cpu) < delta_rusage->cpu_time_msecs)
++ rec.cpu_time_msecs = UINT32_MAX;
++ else
++ rec.cpu_time_msecs = old_cpu + delta_rusage->cpu_time_msecs;
++ rec.flags |= flags_to_set;
++ rec.update_time = ioloop_time;
++
++ /* Round CPU time up to whole seconds for persistence. */
++ if (rec.cpu_time_msecs >= UINT32_MAX - 999)
++ cpu_secs = UINT32_MAX / 1000;
++ else
++ cpu_secs = (rec.cpu_time_msecs + 999) / 1000;
++
++ out = t_str_new(64);
++ str_printfa(out, "%s %u %u %lld\n", SIEVE_RUSAGE_VERSION_TAG,
++ rec.flags, cpu_secs, (long long)rec.update_time);
++
++ wret = write(fd, str_data(out), str_len(out));
++ if (wret != (ssize_t)str_len(out)) {
++ e_error(storage->event,
++ "write(%s) failed: %m (wrote %zd/%zu)",
++ storage->rusage_path, wret, str_len(out));
++ file_dotlock_delete(&dotlock);
++ return -1;
++ }
++
++ if (file_dotlock_replace(&dotlock, 0) < 0) {
++ e_error(storage->event,
++ "file_dotlock_replace(%s) failed: %m",
++ storage->rusage_path);
++ return -1;
++ }
++ return 1;
++}
+Index: trixie/pigeonhole/src/lib-sieve/sieve-rusage.h
+===================================================================
+--- /dev/null
++++ trixie/pigeonhole/src/lib-sieve/sieve-rusage.h
+@@ -0,0 +1,32 @@
++#ifndef SIEVE_RUSAGE_H
++#define SIEVE_RUSAGE_H
++
++#include "sieve-common.h"
++
++struct sieve_storage;
++struct mail_user;
++
++/* Per-user resource usage file lives at <INBOX-namespace-root>/sieve-rusage.
++ It survives script renames, deletions, and re-uploads under a different
++ name, preventing CPU-budget bypass. Tracking is enabled only for personal
++ user-controlled storage. */
++
++void sieve_rusage_storage_init(struct sieve_storage *storage,
++ struct mail_user *user);
++
++/* Load persisted resource usage from the per-user file. Returns 1 on success,
++ 0 if the storage has no rusage file (no INBOX namespace path), -1 on error.
++ Applies the resource_usage_timeout decay. flags_r receives sieve binary
++ header flags (RESOURCE_LIMIT) that were persisted. */
++int sieve_rusage_storage_load(struct sieve_storage *storage,
++ struct sieve_resource_usage *rusage_r,
++ uint32_t *flags_r);
++
++/* Atomically add delta_rusage to and OR flags_to_set into the persisted
++ per-user rusage file. Read-modify-write under fcntl WRLCK. Returns 1 on
++ success, 0 if no rusage file is configured, -1 on error. */
++int sieve_rusage_storage_add(struct sieve_storage *storage,
++ const struct sieve_resource_usage *delta_rusage,
++ uint32_t flags_to_set);
++
++#endif
+Index: trixie/pigeonhole/src/lib-sieve/sieve-storage-private.h
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-storage-private.h
++++ trixie/pigeonhole/src/lib-sieve/sieve-storage-private.h
+@@ -118,6 +118,17 @@ struct sieve_storage {
+
+ struct mail_namespace *sync_inbox_ns;
+
++ /* Per-user resource usage tracking. Set when storage is personal and
++ the user's INBOX namespace has a filesystem path. NULL otherwise.
++ inbox_list is kept so the namespace root directory can be created
++ on demand before the rusage file is updated; LDA may close the
++ sieve binary (and therefore persist rusage) before any mail has
++ been delivered, in which case the namespace root may not exist
++ yet. */
++ struct mail_user *user;
++ struct mailbox_list *inbox_list;
++ const char *rusage_path;
++
+ enum sieve_storage_flags flags;
+
+ bool allows_synchronization:1;
+Index: trixie/pigeonhole/src/lib-sieve/sieve-storage.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-storage.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-storage.c
+@@ -17,6 +17,7 @@
+
+ #include "sieve-script-private.h"
+ #include "sieve-storage-private.h"
++#include "sieve-rusage.h"
+
+ #include <sys/types.h>
+ #include <sys/stat.h>
+@@ -777,6 +778,7 @@ int sieve_storage_create_personal(struct
+ if (ret == 0) {
+ i_assert(storage->is_personal);
+ (void)sieve_storage_sync_init(storage, user);
++ sieve_rusage_storage_init(storage, user);
+ } else if (*error_code_r != SIEVE_ERROR_TEMP_FAILURE &&
+ (flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+ (flags & SIEVE_STORAGE_FLAG_READWRITE) == 0) {
+Index: trixie/pigeonhole/src/lib-sieve/sieve.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve.c
++++ trixie/pigeonhole/src/lib-sieve/sieve.c
+@@ -30,6 +30,7 @@
+ #include "sieve-generator.h"
+ #include "sieve-interpreter.h"
+ #include "sieve-binary-dumper.h"
++#include "sieve-rusage.h"
+
+ #include "sieve.h"
+ #include "sieve-common.h"
+@@ -444,6 +445,8 @@ sieve_open_script_real(struct sieve_scri
+
+ /* Try to open the matching binary */
+ if (sieve_script_binary_load(script, &sbin, error_code_r) == 0) {
++ /* Per-user rusage file (if any) overrides binary header */
++ sieve_binary_apply_persisted_rusage(sbin);
+ sieve_binary_get_resource_usage(sbin, &rusage);
+
+ /* Ok, it exists; now let's see if it is up to date */
+@@ -474,6 +477,8 @@ sieve_open_script_real(struct sieve_scri
+ sieve_script_label(script));
+
+ sieve_binary_set_resource_usage(sbin, &rusage);
++ /* Per-user rusage file (if any) overrides any carry-forward */
++ sieve_binary_apply_persisted_rusage(sbin);
+ }
+
+ /* Check whether binary can be executed. */
--- /dev/null
+From 4010e318939b557b0cdf7da3ae0f320d408aea65 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 11 Jun 2026 19:31:45 +0000
+Subject: [PATCH 1/2] lib-sieve: actions - Split mailbox creation into
+ sieve_act_store_create_mailbox()
+
+Pure refactoring with no functional change: move the mailbox_create() /
+subscribe / reopen sequence out of the "mailbox" extension's :create side
+effect (seff_mailbox_create_pre_execute) into a new reusable
+sieve_act_store_create_mailbox() helper.
+---
+ .../plugins/mailbox/tag-mailbox-create.c | 32 +---------------
+ src/lib-sieve/sieve-actions.c | 38 +++++++++++++++++++
+ src/lib-sieve/sieve-actions.h | 5 +++
+ 3 files changed, 45 insertions(+), 30 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/plugins/mailbox/tag-mailbox-create.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/plugins/mailbox/tag-mailbox-create.c
++++ trixie/pigeonhole/src/lib-sieve/plugins/mailbox/tag-mailbox-create.c
+@@ -150,36 +150,8 @@ seff_mailbox_create_pre_execute(
+ return SIEVE_EXEC_FAILURE;
+ }
+
+- trans->error = NULL;
+- trans->error_code = MAIL_ERROR_NONE;
+-
+- /* Create mailbox */
+- if (mailbox_create(box, NULL, FALSE) < 0) {
+- sieve_act_store_get_storage_error(aenv, trans);
+- if (trans->error_code == MAIL_ERROR_EXISTS) {
+- trans->error = NULL;
+- trans->error_code = MAIL_ERROR_NONE;
+- } else {
+- return (trans->error_code == MAIL_ERROR_TEMP ?
+- SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+- }
+- }
+-
+- /* Subscribe to it if necessary */
+- if (eenv->scriptenv->mailbox_autosubscribe) {
+- (void)mailbox_list_set_subscribed(
+- mailbox_get_namespace(box)->list,
+- mailbox_get_name(box), TRUE);
+- }
+-
+- /* Try opening again */
+- if (mailbox_open(box) < 0) {
+- /* Failed definitively */
+- sieve_act_store_get_storage_error(aenv, trans);
+- return (trans->error_code == MAIL_ERROR_TEMP ?
+- SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+- }
+- return SIEVE_EXEC_OK;
++ /* Create the mailbox */
++ return sieve_act_store_create_mailbox(aenv, trans);
+ }
+
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-actions.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-actions.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-actions.c
+@@ -372,6 +372,44 @@ void sieve_act_store_get_storage_error(c
+ &trans->error_code));
+ }
+
++int sieve_act_store_create_mailbox(const struct sieve_action_exec_env *aenv,
++ struct act_store_transaction *trans)
++{
++ const struct sieve_execute_env *eenv = aenv->exec_env;
++ struct mailbox *box = trans->box;
++
++ trans->error = NULL;
++ trans->error_code = MAIL_ERROR_NONE;
++
++ /* Create mailbox */
++ if (mailbox_create(box, NULL, FALSE) < 0) {
++ sieve_act_store_get_storage_error(aenv, trans);
++ if (trans->error_code == MAIL_ERROR_EXISTS) {
++ trans->error = NULL;
++ trans->error_code = MAIL_ERROR_NONE;
++ } else {
++ return (trans->error_code == MAIL_ERROR_TEMP ?
++ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
++ }
++ }
++
++ /* Subscribe to it if necessary */
++ if (eenv->scriptenv->mailbox_autosubscribe) {
++ (void)mailbox_list_set_subscribed(
++ mailbox_get_namespace(box)->list,
++ mailbox_get_name(box), TRUE);
++ }
++
++ /* Try opening again */
++ if (mailbox_open(box) < 0) {
++ /* Failed definitively */
++ sieve_act_store_get_storage_error(aenv, trans);
++ return (trans->error_code == MAIL_ERROR_TEMP ?
++ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
++ }
++ return SIEVE_EXEC_OK;
++}
++
+ static bool
+ act_store_mailbox_alloc(const struct sieve_action_exec_env *aenv,
+ const char *mailbox, struct mailbox **box_r,
+Index: trixie/pigeonhole/src/lib-sieve/sieve-actions.h
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-actions.h
++++ trixie/pigeonhole/src/lib-sieve/sieve-actions.h
+@@ -246,6 +246,11 @@ void sieve_act_store_add_flags(const str
+ void sieve_act_store_get_storage_error(const struct sieve_action_exec_env *aenv,
+ struct act_store_transaction *trans);
+
++/* Create the target mailbox of the store transaction (and subscribe to it
++ when configured). */
++int sieve_act_store_create_mailbox(const struct sieve_action_exec_env *aenv,
++ struct act_store_transaction *trans);
++
+ /*
+ * Redirect action
+ */
--- /dev/null
+From 3f0af33abcc9911b586be9b5adc0afb3e9f18d25 Mon Sep 17 00:00:00 2001
+From: Stephan Bosch <stephan.bosch@open-xchange.com>
+Date: Fri, 10 Apr 2026 02:27:17 +0200
+Subject: [PATCH] lib-sieve: sieve-binary-code - Fix single byte oob stack
+ buffer write in sieve_binary_emit_integer()
+
+---
+ src/lib-sieve/sieve-binary-code.c | 2 +-
+ tests/test-size.svtest | 9 +++++++++
+ 2 files changed, 10 insertions(+), 1 deletion(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary-code.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary-code.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary-code.c
+@@ -124,7 +124,7 @@ sieve_size_t sieve_binary_emit_integer(s
+ sieve_number_t integer)
+ {
+ sieve_size_t address = _sieve_binary_block_get_size(sblock);
+- uint8_t buffer[sizeof(sieve_number_t) + 1];
++ uint8_t buffer[sizeof(sieve_number_t) * 8 / 7 + 1];
+ int bufpos = sizeof(buffer) - 1;
+
+ /* Encode last byte [0xxxxxxx]; msb == 0 marks the last byte */
+Index: trixie/pigeonhole/tests/test-size.svtest
+===================================================================
+--- trixie.orig/pigeonhole/tests/test-size.svtest
++++ trixie/pigeonhole/tests/test-size.svtest
+@@ -72,3 +72,12 @@ test "Exact size" {
+ }
+ }
+
++/*
++ * TEST: Extreme size
++ */
++
++test "Extreme size" {
++ if size :over 18446744073709551615 {
++ test_fail "size :over matched extreme size";
++ }
++}
--- /dev/null
+From d82c2e624e4a9c364a6fa58fad0938ae5578660c Mon Sep 17 00:00:00 2001
+From: Stephan Bosch <stephan.bosch@open-xchange.com>
+Date: Tue, 25 Nov 2025 05:07:16 +0100
+Subject: [PATCH] lib-sieve: sieve-storage - Add is_personal bit
+
+Simpler than evaluating the type and personal storage is created only through
+sieve_storage_create_personal(). This mimics the bit for default storage.
+---
+ src/lib-sieve/sieve-storage-private.h | 1 +
+ src/lib-sieve/sieve-storage.c | 3 ++-
+ src/lib-sieve/storage/file/sieve-file-storage.c | 6 ++----
+ 3 files changed, 5 insertions(+), 5 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-storage-private.h
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-storage-private.h
++++ trixie/pigeonhole/src/lib-sieve/sieve-storage-private.h
+@@ -121,6 +121,7 @@ struct sieve_storage {
+ enum sieve_storage_flags flags;
+
+ bool allows_synchronization:1;
++ bool is_personal:1;
+ bool is_default:1;
+ };
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-storage.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-storage.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-storage.c
+@@ -773,6 +773,7 @@ int sieve_storage_create_personal(struct
+ &storage, error_code_r, NULL);
+ if (ret == 0) {
+ (void)sieve_storage_sync_init(storage, user);
++ storage->is_personal = TRUE;
+ } else if (*error_code_r != SIEVE_ERROR_TEMP_FAILURE &&
+ (flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+ (flags & SIEVE_STORAGE_FLAG_READWRITE) == 0) {
+@@ -1017,7 +1018,7 @@ sieve_storage_get_default_script(struct
+
+ if (*error_code_r != SIEVE_ERROR_NOT_FOUND ||
+ (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 ||
+- !sieve_storage_is_personal(storage))
++ !storage->is_personal)
+ return -1;
+
+ /* Not found; if this name maps to the default script,
+Index: trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c
++++ trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c
+@@ -459,11 +459,9 @@ sieve_file_storage_init_from_settings(
+
+ /* Stat storage directory */
+
+- bool is_personal = sieve_storage_is_personal(storage);
+-
+ if (storage_path != NULL && *storage_path != '\0') {
+ if (sieve_file_storage_stat(fstorage, storage_path) < 0) {
+- if (!is_personal ||
++ if (!storage->is_personal ||
+ storage->error_code != SIEVE_ERROR_NOT_FOUND)
+ return -1;
+ if ((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) == 0) {
+@@ -511,7 +509,7 @@ sieve_file_storage_init_from_settings(
+ }
+
+ if ((active_path == NULL || *active_path == '\0') &&
+- (is_personal ||
++ (storage->is_personal ||
+ (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0)) {
+ e_debug(storage->event,
+ "Active script path is unconfigured; "
--- /dev/null
+From 4d0cbfe6260d567ebc34a747c29bb4e140124d93 Mon Sep 17 00:00:00 2001
+From: Stephan Bosch <stephan.bosch@open-xchange.com>
+Date: Wed, 4 Mar 2026 21:53:36 +0100
+Subject: [PATCH] lib-sieve: sieve-storage - Fix panic caused by flawed
+ detection of personal storage type
+
+Recent commit d82c2e624e4a9c364a6fa58fad0938ae5578660c added the is_personal
+flag, but it is assigned too late. Fix this issue by assigning this flag in the
+earliest allocation function.
+
+This issue commonly surfaces when people forget to set an appropriate type for a
+script storage definition, in which case the "personal" type is the default.
+
+Panic was:
+Panic: file sieve-file-storage-active.c: line 33
+(sieve_file_storage_active_read_link): assertion failed: (fstorage->active_path != NULL)
+---
+ src/lib-sieve/sieve-storage.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-storage.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-storage.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-storage.c
+@@ -232,6 +232,9 @@ sieve_storage_alloc_from_class(struct si
+ storage->pool, "auto:", storage->type, NULL);
+ }
+
++ if (strcasecmp(script_type, SIEVE_STORAGE_TYPE_PERSONAL) == 0)
++ storage->is_personal = TRUE;
++
+ storage->event = event;
+ event_ref(event);
+
+@@ -772,8 +775,8 @@ int sieve_storage_create_personal(struct
+ SIEVE_STORAGE_TYPE_PERSONAL, flags,
+ &storage, error_code_r, NULL);
+ if (ret == 0) {
++ i_assert(storage->is_personal);
+ (void)sieve_storage_sync_init(storage, user);
+- storage->is_personal = TRUE;
+ } else if (*error_code_r != SIEVE_ERROR_TEMP_FAILURE &&
+ (flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+ (flags & SIEVE_STORAGE_FLAG_READWRITE) == 0) {
+@@ -1894,7 +1897,7 @@ bool sieve_storage_is_default(const stru
+
+ bool sieve_storage_is_personal(struct sieve_storage *storage)
+ {
+- return (strcasecmp(storage->type, SIEVE_STORAGE_TYPE_PERSONAL) == 0);
++ return storage->is_personal;
+ }
+
+ /*
--- /dev/null
+From abfb0c1fe180f0edd5527c05cb7dd97840a31ea1 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 4 May 2026 13:09:37 +0000
+Subject: [PATCH 1/2] lib-sieve: storage: file - Refuse symlinks escaping
+ personal storage directory
+
+Pigeonhole's file storage followed any symlink encountered while resolving a
+script path, including symlinks in personal (user-writable) storage whose
+target lay outside the storage directory. In some non-recommended
+configurations a user could exploit this through the include extension:
+an "include :personal name;" lookup of ~/sieve/name.sieve transparently
+followed a user-placed to e.g. another user's file readable by the mail
+process, leaking its contents (or causing it to be parsed as Sieve).
+Normally this shouldn't be possible, because sieve processes shouldn't
+have any more privileges to read files than the local system user creating
+the symlink.
+
+Open the canonical (realpath'd) personal storage directory at storage init
+time and keep an O_DIRECTORY|O_CLOEXEC fd to it. Resolve script content
+reads through this fd by routing sieve_file_script_get_stream() via a new
+sieve_file_storage_open_safe() wrapper around t_openat_safe(), which:
+
+ - opens each path component with O_NOFOLLOW so symlinks are detected
+ explicitly rather than transparently followed;
+ - follows symlinks only when their (recursively resolved) target stays
+ beneath dir_fd, refusing absolute targets and `..` past the storage
+ root with ELOOP;
+ - caps the symlink-hop count to bound resolution time.
+
+Anchoring at dir_fd makes the lookup TOCTOU-safe even when intermediate
+path components are mutated on disk concurrently: resolution stays
+relative to the original directory inode and the safe walker still rejects
+any target that leaves it.
+
+Apply the protection only when storage->is_personal is set; admin-managed
+global storage is trusted and may legitimately use cross-boundary symlinks.
+Single-file storages (is_file=TRUE) keep dir_fd at -1 and fall back to the
+existing open path. Also guard the dir_fd open with S_ISDIR() to handle the
+autodetect quirk where storage_path can refer to a regular file even when
+is_file is FALSE.
+---
+ .../storage/file/sieve-file-script.c | 39 ++++++++--
+ .../storage/file/sieve-file-storage.c | 74 +++++++++++++++++++
+ .../storage/file/sieve-file-storage.h | 32 ++++++++
+ 3 files changed, 140 insertions(+), 5 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
++++ trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
+@@ -441,15 +441,44 @@ sieve_file_script_get_stream(struct siev
+ {
+ struct sieve_file_script *fscript =
+ container_of(script, struct sieve_file_script, script);
++ struct sieve_file_storage *fstorage =
++ container_of(script->storage, struct sieve_file_storage,
++ storage);
+ struct stat st;
+ struct istream *result;
++ const char *error;
+ int fd;
+
+- fd = open(fscript->path, O_RDONLY);
+- if (fd < 0) {
+- sieve_file_script_handle_error(fscript, "open", fscript->path,
+- fscript->script.name);
+- return -1;
++ /* For directory-based storage, open the script via the storage
++ directory fd so that path resolution refuses to follow symlinks
++ whose (recursive) target leaves the storage directory.
++ Single-file storages have no dir_fd, so fall back to plain open(). */
++ if (fstorage->dir_fd >= 0 && fscript->filename != NULL &&
++ *fscript->filename != '\0') {
++ if (sieve_file_storage_open_safe(fstorage, fscript->filename,
++ O_RDONLY, &fd, &error) < 0) {
++ if (errno == ELOOP) {
++ sieve_script_set_critical(
++ script,
++ "Failed to open sieve script: %s",
++ error);
++ script->storage->error_code =
++ SIEVE_ERROR_NO_PERMISSION;
++ return -1;
++ }
++ sieve_file_script_handle_error(fscript, "open",
++ fscript->path,
++ fscript->script.name);
++ return -1;
++ }
++ } else {
++ fd = open(fscript->path, O_RDONLY);
++ if (fd < 0) {
++ sieve_file_script_handle_error(fscript, "open",
++ fscript->path,
++ fscript->script.name);
++ return -1;
++ }
+ }
+
+ if (fstat(fd, &st) != 0) {
+Index: trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c
++++ trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c
+@@ -20,6 +20,7 @@
+ #include <stdio.h>
+ #include <unistd.h>
+ #include <ctype.h>
++#include <fcntl.h>
+ #include <utime.h>
+ #include <sys/time.h>
+
+@@ -42,6 +43,33 @@ sieve_file_storage_path_extend(struct si
+ return t_strconcat(path, "/", filename , NULL);
+ }
+
++int sieve_file_storage_open_safe(struct sieve_file_storage *fstorage,
++ const char *path, int flags, int *fd_r,
++ const char **error_r)
++{
++ const char *walk_error;
++ int fd;
++
++ *fd_r = -1;
++
++ if (fstorage->dir_fd < 0) {
++ *error_r = "storage directory fd not available";
++ errno = ENOTSUP;
++ return -1;
++ }
++
++ fd = t_openat_safe(fstorage->dir_fd, path, flags, &walk_error);
++ if (fd < 0) {
++ *error_r = t_strdup_printf(
++ "Failed to open '%s/%s': %s",
++ fstorage->path, path, walk_error);
++ return -1;
++ }
++
++ *fd_r = fd;
++ return 0;
++}
++
+ /*
+ *
+ */
+@@ -215,10 +243,19 @@ static struct sieve_storage *sieve_file_
+ fstorage = p_new(pool, struct sieve_file_storage, 1);
+ fstorage->storage = sieve_file_storage;
+ fstorage->storage.pool = pool;
++ fstorage->dir_fd = -1;
+
+ return &fstorage->storage;
+ }
+
++static void sieve_file_storage_destroy(struct sieve_storage *storage)
++{
++ struct sieve_file_storage *fstorage =
++ container_of(storage, struct sieve_file_storage, storage);
++
++ i_close_fd(&fstorage->dir_fd);
++}
++
+ static int
+ sieve_file_storage_get_full_path(struct sieve_file_storage *fstorage,
+ const char **storage_path)
+@@ -436,6 +473,42 @@ sieve_file_storage_init_common(struct si
+ fstorage->link_path =
+ p_strdup(storage->pool, link_path);
+ }
++
++ /* For personal (user-writable) storage, open a fd to the
++ canonical storage directory. This fd anchors TOCTOU-safe
++ path resolution for script lookups: the inode it refers to
++ cannot change underneath us, so even if the user mutates
++ intermediate path components on disk afterwards, we still
++ resolve relative to the original directory. The walker that
++ uses this fd refuses script paths that escape the storage
++ directory through symlinks or `..`, which would otherwise
++ let an unprivileged user redirect e.g. include "name" to a
++ file outside their own sieve storage.
++
++ Non-personal (e.g. admin-configured global) storage is
++ trusted; dir_fd stays unset there so legitimate
++ admin-managed symlinks crossing the storage boundary keep
++ working.
++
++ Only open the fd when storage_path actually resolves to a
++ directory: autodetect can leave is_file=FALSE while
++ storage_path still points at a regular file (see the
++ storage_path == active_path fallback in
++ sieve_file_storage_do_autodetect()). In that case there is
++ no directory to anchor to and lookups by name will fail
++ with ENOENT later anyway. */
++ if (storage->is_personal &&
++ S_ISDIR(fstorage->st.st_mode)) {
++ fstorage->dir_fd = open(storage_path,
++ O_RDONLY | O_DIRECTORY |
++ O_CLOEXEC);
++ if (fstorage->dir_fd < 0) {
++ sieve_storage_set_critical(storage,
++ "Failed to open storage directory: "
++ "open(%s) failed: %m", storage_path);
++ return -1;
++ }
++ }
+ }
+
+ fstorage->path = p_strdup(storage->pool, storage_path);
+@@ -904,6 +977,7 @@ const struct sieve_storage sieve_file_st
+ .v = {
+ .alloc = sieve_file_storage_alloc,
+ .init = sieve_file_storage_init,
++ .destroy = sieve_file_storage_destroy,
+
+ .autodetect = sieve_file_storage_autodetect,
+
+Index: trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.h
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.h
++++ trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.h
+@@ -41,6 +41,13 @@ struct sieve_file_storage {
+
+ time_t prev_mtime;
+
++ /* fd referencing the canonical (realpath'd) storage directory. Used to
++ anchor TOCTOU-safe path resolution for script files: lookups that
++ escape this directory via symlinks (or `..`) are refused. -1 if the
++ storage is a single-file storage or this protection is unavailable.
++ */
++ int dir_fd;
++
+ bool is_file:1;
+ };
+
+@@ -60,6 +67,31 @@ int sieve_file_storage_init_from_path(st
+
+ int sieve_file_storage_pre_modify(struct sieve_storage *storage);
+
++/* Open a script file that lives under the storage directory, refusing any
++ path resolution that escapes that directory through symlinks or `..`.
++
++ The path is resolved component-by-component using openat() relative to
++ fstorage->dir_fd, with O_NOFOLLOW per component. Symlinks are followed
++ only if their (recursively resolved) target also stays beneath dir_fd;
++ absolute symlink targets and `..` past the storage root are refused.
++
++ Anchoring the resolution at dir_fd makes the check TOCTOU-safe: even if
++ the user mutates path components on disk between calls, dir_fd still
++ refers to the original directory inode.
++
++ `flags` is OR-ed into the openat() call for the final component (e.g.
++ O_RDONLY). O_NOFOLLOW and O_CLOEXEC are added automatically.
++
++ Returns 0 on success and stores the new fd in *fd_r. Returns -1 on
++ failure with errno set; *error_r is set to a descriptive message.
++ errno=ELOOP indicates either too many symlinks or an attempted escape.
++ errno=ENOTSUP indicates fstorage->dir_fd is not available; the caller
++ may fall back to opening fstorage->path directly.
++ */
++int sieve_file_storage_open_safe(struct sieve_file_storage *fstorage,
++ const char *path, int flags, int *fd_r,
++ const char **error_r);
++
+ /* Active script */
+
+ int sieve_file_storage_active_replace_link(struct sieve_file_storage *fstorage,
--- /dev/null
+From adcd0f9553c6d07142b97766fa0b53d56112c15c Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 14 Apr 2026 12:58:08 +0200
+Subject: [PATCH 1/3] lib-sieve: util: edit-mail - Fix writing to freed memory
+
+---
+ Makefile.am | 1 +
+ src/lib-sieve/util/edit-mail.c | 12 ++-
+ .../editheader/deleteheader-snapshot.svtest | 76 +++++++++++++++++++
+ 3 files changed, 88 insertions(+), 1 deletion(-)
+ create mode 100644 tests/extensions/editheader/deleteheader-snapshot.svtest
+
+Index: trixie/pigeonhole/Makefile.am
+===================================================================
+--- trixie.orig/pigeonhole/Makefile.am
++++ trixie/pigeonhole/Makefile.am
+@@ -164,6 +164,7 @@ test_cases = \
+ tests/extensions/ihave/restrictions.svtest \
+ tests/extensions/editheader/addheader.svtest \
+ tests/extensions/editheader/deleteheader.svtest \
++ tests/extensions/editheader/deleteheader-snapshot.svtest \
+ tests/extensions/editheader/alternating.svtest \
+ tests/extensions/editheader/utf8.svtest \
+ tests/extensions/editheader/protected.svtest \
+Index: trixie/pigeonhole/src/lib-sieve/util/edit-mail.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/util/edit-mail.c
++++ trixie/pigeonhole/src/lib-sieve/util/edit-mail.c
+@@ -890,7 +890,17 @@ static int edit_mail_headers_parse(struc
+ }
+ }
+
+- /* Rebuild header index */
++ /* Rebuild header index. Reset first pointers before rebuilding so that
++ the actual first occurrence in the final list is used. Without this,
++ a snapshot's pre-set first pointer may refer to an appended field
++ that sits later in the list than a parsed field with the same header,
++ causing deleteheader to miss the parsed fields while still freeing
++ the header_index — leaving stale field_idx->header pointers. */
++ struct _header_index *hidx = edmail->headers_head;
++ while (hidx != NULL) {
++ hidx->first = NULL;
++ hidx = hidx->next;
++ }
+ current = edmail->header_fields_head;
+ while (current != NULL) {
+ if (current->header->first == NULL)
+Index: trixie/pigeonhole/tests/extensions/editheader/deleteheader-snapshot.svtest
+===================================================================
+--- /dev/null
++++ trixie/pigeonhole/tests/extensions/editheader/deleteheader-snapshot.svtest
+@@ -0,0 +1,76 @@
++require "vnd.dovecot.testsuite";
++require "editheader";
++require "fileinto";
++require "mailbox";
++
++/*
++ * Regression test: heap use-after-free in edit_mail_snapshot when
++ * deleteheader is called after a fileinto snapshot on an edit_mail
++ * whose headers have not yet been parsed.
++ */
++
++test_set "message" text:
++From: sender@example.com
++To: recipient@example.com
++Cc: original@example.com
++Subject: Test
++
++Body.
++.
++;
++
++test "deleteheader all: snapshot stale first pointer after parse" {
++ /* Add a Cc header while headers are not yet parsed. The snapshot
++ * created by the next fileinto has H_clone_cc->first pointing at
++ * this appended field. */
++ addheader :last "Cc" "appended@example.com";
++
++ /* fileinto sets edit_snapshot=TRUE. The next call to
++ * sieve_message_edit creates a snapshot (edmail_snap) and the
++ * fileinto action retains the pre-snapshot version for delivery. */
++ fileinto :create "folder1";
++
++ /* sieve_message_edit creates edmail_snap here. edit_mail_headers_parse
++ * inserts the parsed "Cc: original@example.com" before the appended
++ * field in the list. Without the fix the rebuild skips resetting
++ * ->first; the delete loop then only removes the appended field,
++ * frees H_clone_cc (index==0), and leaves the parsed field_idx with
++ * a dangling ->header pointer. */
++ deleteheader "Cc";
++
++ /* A second fileinto + edit operation takes another snapshot of the
++ * same edmail_snap. edit_mail_snapshot walks header_fields_head and
++ * reads field_idx->header->header on the orphaned entry.
++ * Without the fix: use-after-free crash.
++ * With the fix: clean run, both Cc occurrences are correctly gone. */
++ fileinto :create "folder2";
++ deleteheader "Subject";
++
++ if not test_result_execute {
++ test_fail "failed to execute result";
++ }
++
++ /* folder1 was snapshotted before deleteheader "Cc": both Cc headers
++ * must be present. */
++ if not test_message :folder "folder1" 0 {
++ test_fail "message not stored in folder1";
++ }
++
++ if not header :is "Cc" "original@example.com" {
++ test_fail "original Cc missing in folder1 snapshot";
++ }
++
++ if not header :is "Cc" "appended@example.com" {
++ test_fail "appended Cc missing in folder1 snapshot";
++ }
++
++ /* folder2 was snapshotted after deleteheader "Cc": both Cc headers
++ * must be absent. */
++ if not test_message :folder "folder2" 0 {
++ test_fail "message not stored in folder2";
++ }
++
++ if exists "Cc" {
++ test_fail "Cc header not fully deleted in folder2 snapshot";
++ }
++}
--- /dev/null
+From 7aa8c147eb15009b56c85a00c33c2c0b972c1398 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Fri, 29 May 2026 11:21:00 +0000
+Subject: [PATCH] lib-sql: Add sql_result_new_error() helper
+
+---
+ src/lib-sql/sql-api.c | 43 +++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 43 insertions(+)
+
+diff --git a/src/lib-sql/sql-api.c b/src/lib-sql/sql-api.c
+index 3d4518c3cc..4db6913229 100644
+--- a/src/lib-sql/sql-api.c
++++ b/src/lib-sql/sql-api.c
+@@ -66,6 +66,49 @@ ARRAY_TYPE(sql_drivers) sql_drivers;
+ static void sql_query_delayed_callback(struct sql_query_result_delayed *cb);
+ static void sql_commit_delayed_callback(struct sql_commit_result_delayed *cb);
+
++struct sql_result_error {
++ struct sql_result result;
++ char *error;
++};
++
++static void sql_result_error_free(struct sql_result *_result)
++{
++ struct sql_result_error *result =
++ container_of(_result, struct sql_result_error, result);
++ i_free(result->error);
++ i_free(result);
++}
++
++static int sql_result_error_next_row(struct sql_result *result ATTR_UNUSED)
++{
++ return -1;
++}
++
++static const char *
++sql_result_error_get_error(struct sql_result *_result)
++{
++ struct sql_result_error *result =
++ container_of(_result, struct sql_result_error, result);
++ return result->error;
++}
++
++static const struct sql_result_vfuncs sql_result_error_vfuncs = {
++ .free = sql_result_error_free,
++ .next_row = sql_result_error_next_row,
++ .get_error = sql_result_error_get_error,
++};
++
++static struct sql_result *sql_result_new_error(const char *error) ATTR_UNUSED;
++static struct sql_result *sql_result_new_error(const char *error)
++{
++ struct sql_result_error *result = i_new(struct sql_result_error, 1);
++ result->result.v = sql_result_error_vfuncs;
++ result->result.failed = TRUE;
++ result->result.refcount = 1;
++ result->error = i_strdup(error);
++ return &result->result;
++}
++
+ void sql_drivers_init_without_drivers(void)
+ {
+ i_array_init(&sql_drivers, 8);
+--
+2.47.3
+
--- /dev/null
+From 98282f1a02d29d6fdd0b3db63ae398d0162fe7bd Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Mon, 18 Aug 2025 08:30:42 +0300
+Subject: [PATCH] lib-sql: Use strchr() based while loop to fill template
+
+This is much faster than going one byte at a time.
+---
+ src/lib-sql/sql-api.c | 26 ++++++++++++++------------
+ src/lib-sql/test-sql.c | 30 ++++++++++++++++++++++++++++++
+ 2 files changed, 44 insertions(+), 12 deletions(-)
+
+Index: trixie/src/lib-sql/sql-api.c
+===================================================================
+--- trixie.orig/src/lib-sql/sql-api.c
++++ trixie/src/lib-sql/sql-api.c
+@@ -400,22 +400,24 @@ const char *sql_statement_get_query(stru
+ {
+ string_t *query = t_str_new(128);
+ const char *const *args;
+- unsigned int i, args_count, arg_pos = 0;
++ unsigned int args_count, arg_pos = 0;
++ const char *p0, *p1;
+
+ args = array_get(&stmt->args, &args_count);
+-
+- for (i = 0; stmt->query_template[i] != '\0'; i++) {
+- if (stmt->query_template[i] == '?') {
+- if (arg_pos >= args_count ||
+- args[arg_pos] == NULL) {
+- i_panic("lib-sql: Missing bind for arg #%u in statement: %s",
+- arg_pos, stmt->query_template);
+- }
+- str_append(query, args[arg_pos++]);
+- } else {
+- str_append_c(query, stmt->query_template[i]);
++ p0 = stmt->query_template;
++ while ((p1 = strchr(p0, '?')) != NULL) {
++ /* append until ? */
++ str_append_max(query, p0, (p1 - p0));
++ if (arg_pos >= args_count ||
++ args[arg_pos] == NULL) {
++ i_panic("lib-sql: Missing bind for arg #%u in statement: %s",
++ arg_pos, stmt->query_template);
+ }
++ str_append(query, args[arg_pos++]);
++ p0 = p1 + 1;
+ }
++ str_append(query, p0);
++
+ if (arg_pos != args_count) {
+ i_panic("lib-sql: Too many bind args (%u) for statement: %s",
+ args_count, stmt->query_template);
+Index: trixie/src/lib-sql/test-sql.c
+===================================================================
+--- trixie.orig/src/lib-sql/test-sql.c
++++ trixie/src/lib-sql/test-sql.c
+@@ -60,6 +60,24 @@ static void deinit_sql(struct sql_db **_
+ }; \
+ sql_driver_test_add_expected_result(sql, &result_1);
+
++#define setup_result_2(sql) \
++ struct test_driver_result_set rset_2 = { \
++ .rows = 2, \
++ .cols = 1, \
++ .col_names = (const char *[]){"foo", NULL}, \
++ .row_data = (const char **[]){ \
++ (const char*[]){"value1", NULL}, \
++ (const char*[]){"value2", NULL}, \
++ }, \
++ }; \
++ struct test_driver_result result_2 = { \
++ .nqueries = 1, \
++ .queries = (const char *[]){"SELECT foo FROM bar WHERE baz = 'foz'"}, \
++ .result = &rset_2 \
++ }; \
++ sql_driver_test_add_expected_result(sql, &result_2);
++
++
+ static void test_result_1(struct sql_result *cursor)
+ {
+ test_assert(sql_result_next_row(cursor) == SQL_RESULT_NEXT_OK);
+@@ -125,6 +143,18 @@ static void test_sql_stmt_prepared_api(v
+
+ test_result_1(cursor);
+
++ sql_result_unref(cursor);
++
++ setup_result_2(sql);
++
++ prep_stmt = sql_prepared_statement_init(sql, "SELECT foo FROM bar WHERE baz = ?");
++ stmt = sql_statement_init_prepared(prep_stmt);
++ sql_statement_bind_str(stmt, 0, "foz");
++ sql_prepared_statement_unref(&prep_stmt);
++ cursor = sql_statement_query_s(&stmt);
++
++ test_result_1(cursor);
++
+ sql_result_unref(cursor);
+
+ deinit_sql(&sql);
--- /dev/null
+From ff926d0c9288f343d18a3c9ec20cf1e252d52286 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Fri, 7 Nov 2025 09:21:01 +0200
+Subject: [PATCH] lib-sql: driver-sqlite - Use sqlite3_snprintf() to quote
+ values
+
+This does it the sqlite3 way.
+---
+ src/lib-sql/driver-sqlite.c | 29 +++++------------------------
+ 1 file changed, 5 insertions(+), 24 deletions(-)
+
+Index: trixie/src/lib-sql/driver-sqlite.c
+===================================================================
+--- trixie.orig/src/lib-sql/driver-sqlite.c
++++ trixie/src/lib-sql/driver-sqlite.c
+@@ -225,30 +225,11 @@ static const char *
+ driver_sqlite_escape_string(struct sql_db *_db ATTR_UNUSED,
+ const char *string)
+ {
+- const char *p;
+- char *dest, *destbegin;
+-
+- /* find the first ' */
+- for (p = string; *p != '\''; p++) {
+- if (*p == '\0')
+- return t_strdup_noconst(string);
+- }
+-
+- /* @UNSAFE: escape ' with '' */
+- dest = destbegin = t_buffer_get((p - string) + strlen(string) * 2 + 1);
+-
+- memcpy(dest, string, p - string);
+- dest += p - string;
+-
+- for (; *p != '\0'; p++) {
+- *dest++ = *p;
+- if (*p == '\'')
+- *dest++ = *p;
+- }
+- *dest++ = '\0';
+- t_buffer_alloc(dest - destbegin);
+-
+- return destbegin;
++ const size_t len = strlen(string) * 2 + 1;
++ char *escaped = t_malloc_no0(len);
++ if (sqlite3_snprintf(len, escaped, "%q", string) == NULL)
++ i_unreached();
++ return escaped;
+ }
+
+ static const char *
--- /dev/null
+From a78c015eae67a891a0c14b5434dd1f99838ae4cc Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Mon, 6 Oct 2025 12:39:50 +0300
+Subject: [PATCH] lib-sql: sql-api - Implement async calls for drivers that
+ can't
+
+Changes the behaviour of asynchronous functions to be truly asynchronous
+even if the underlying driver isn't capable of doing this. Implemented
+by adding immediate timeouts to call the callbacks after returning from the
+synchronous function and ending up back to ioloop.
+---
+ src/lib-sql/sql-api-private.h | 7 +++
+ src/lib-sql/sql-api.c | 103 ++++++++++++++++++++++++++++++++--
+ src/lib-sql/sql-api.h | 4 +-
+ 3 files changed, 107 insertions(+), 7 deletions(-)
+
+Index: trixie/src/lib-sql/sql-api-private.h
+===================================================================
+--- trixie.orig/src/lib-sql/sql-api-private.h
++++ trixie/src/lib-sql/sql-api-private.h
+@@ -84,11 +84,15 @@ struct sql_db_vfuncs {
+ const char *(*escape_string)(struct sql_db *db, const char *string);
+
+ void (*exec)(struct sql_db *db, const char *query);
++ /* Only implement this if the driver can really do asynchronous callbacks,
++ otherwise let sql API handle it. */
+ void (*query)(struct sql_db *db, const char *query,
+ sql_query_callback_t *callback, void *context);
+ struct sql_result *(*query_s)(struct sql_db *db, const char *query);
+
+ struct sql_transaction_context *(*transaction_begin)(struct sql_db *db);
++ /* Only implement this if the driver can really do asynchronous callbacks,
++ otherwise let sql API handle it. */
+ void (*transaction_commit)(struct sql_transaction_context *ctx,
+ sql_commit_callback_t *callback,
+ void *context);
+@@ -154,6 +158,9 @@ struct sql_db {
+ struct event *event;
+ HASH_TABLE(char *, struct sql_prepared_statement *) prepared_stmt_hash;
+
++ struct sql_query_result_delayed *query_delayed_list;
++ struct sql_commit_result_delayed *commit_delayed_list;
++
+ enum sql_db_state state;
+ /* last time we started connecting to this server
+ (which may or may not have succeeded) */
+Index: trixie/src/lib-sql/sql-api.c
+===================================================================
+--- trixie.orig/src/lib-sql/sql-api.c
++++ trixie/src/lib-sql/sql-api.c
+@@ -4,6 +4,7 @@
+ #include "array.h"
+ #include "ioloop.h"
+ #include "hash.h"
++#include "llist.h"
+ #include "str.h"
+ #include "time-util.h"
+ #include "settings.h"
+@@ -12,6 +13,28 @@
+
+ #include <time.h>
+
++struct sql_query_result_delayed {
++ struct sql_query_result_delayed *prev, *next;
++
++ struct sql_db *db;
++ struct timeout *to;
++
++ struct sql_result *result;
++ sql_query_callback_t *callback;
++ void *context;
++};
++
++struct sql_commit_result_delayed {
++ struct sql_commit_result_delayed *prev, *next;
++
++ struct sql_db *db;
++ struct timeout *to;
++
++ char *error;
++ sql_commit_callback_t *callback;
++ void *context;
++};
++
+ struct event_category event_category_sql = {
+ .name = "sql",
+ };
+@@ -40,6 +63,9 @@ const struct setting_parser_info sql_set
+ struct sql_db_module_register sql_db_module_register = { 0 };
+ ARRAY_TYPE(sql_drivers) sql_drivers;
+
++static void sql_query_delayed_callback(struct sql_query_result_delayed *cb);
++static void sql_commit_delayed_callback(struct sql_commit_result_delayed *cb);
++
+ void sql_drivers_init_without_drivers(void)
+ {
+ i_array_init(&sql_drivers, 8);
+@@ -228,9 +254,19 @@ int sql_connect(struct sql_db *db)
+ return db->v.connect(db);
+ }
+
++static void sql_call_delayed_callbacks(struct sql_db *db)
++{
++ /* flush any pending results */
++ while (db->query_delayed_list != NULL)
++ sql_query_delayed_callback(db->query_delayed_list);
++ while (db->commit_delayed_list != NULL)
++ sql_commit_delayed_callback(db->commit_delayed_list);
++}
++
+ void sql_disconnect(struct sql_db *db)
+ {
+ timeout_remove(&db->to_reconnect);
++ sql_call_delayed_callbacks(db);
+ db->v.disconnect(db);
+ }
+
+@@ -250,11 +286,31 @@ void sql_exec(struct sql_db *db, const c
+ db->v.exec(db, query);
+ }
+
++static void sql_query_delayed_callback(struct sql_query_result_delayed *cb)
++{
++ timeout_remove(&cb->to);
++ DLLIST_REMOVE(&cb->db->query_delayed_list, cb);
++ cb->callback(cb->result, cb->context);
++ sql_result_unref(cb->result);
++ i_free(cb);
++}
++
+ #undef sql_query
+ void sql_query(struct sql_db *db, const char *query,
+ sql_query_callback_t *callback, void *context)
+ {
+- db->v.query(db, query, callback, context);
++ if (db->v.query != NULL) {
++ db->v.query(db, query, callback, context);
++ return;
++ }
++
++ struct sql_query_result_delayed *cb = i_new(struct sql_query_result_delayed, 1);
++ cb->db = db;
++ cb->result = sql_query_s(db, query);
++ cb->callback = callback;
++ cb->context = context;
++ cb->to = timeout_add_short(0, sql_query_delayed_callback, cb);
++ DLLIST_PREPEND(&db->query_delayed_list, cb);
+ }
+
+ struct sql_result *sql_query_s(struct sql_db *db, const char *query)
+@@ -494,11 +550,21 @@ void sql_statement_query(struct sql_stat
+ sql_query_callback_t *callback, void *context)
+ {
+ struct sql_statement *stmt = *_stmt;
+-
+ *_stmt = NULL;
++
+ if (stmt->db->v.statement_query != NULL)
+ stmt->db->v.statement_query(stmt, callback, context);
+- else
++ else if (stmt->db->v.statement_query_s != NULL) {
++ struct sql_db *db = stmt->db;
++ struct sql_query_result_delayed *cb =
++ i_new(struct sql_query_result_delayed, 1);
++ cb->db = db;
++ cb->callback = callback;
++ cb->context = context;
++ cb->result = sql_statement_query_s(&stmt);
++ cb->to = timeout_add_short(0, sql_query_delayed_callback, cb);
++ DLLIST_PREPEND(&db->query_delayed_list, cb);
++ } else
+ default_sql_statement_query(stmt, callback, context);
+ }
+
+@@ -755,14 +821,40 @@ void sql_transaction_set_non_atomic(stru
+ ctx->non_atomic = TRUE;
+ }
+
++static void sql_commit_delayed_callback(struct sql_commit_result_delayed *cb)
++{
++ struct sql_commit_result result = {
++ .error = cb->error,
++ };
++ timeout_remove(&cb->to);
++ DLLIST_REMOVE(&cb->db->commit_delayed_list, cb);
++ cb->callback(&result, cb->context);
++ i_free(cb->error);
++ i_free(cb);
++}
++
+ #undef sql_transaction_commit
+ void sql_transaction_commit(struct sql_transaction_context **_ctx,
+ sql_commit_callback_t *callback, void *context)
+ {
+ struct sql_transaction_context *ctx = *_ctx;
+-
++ struct sql_db *db = ctx->db;
+ *_ctx = NULL;
+- ctx->db->v.transaction_commit(ctx, callback, context);
++
++ if (ctx->db->v.transaction_commit != NULL) {
++ ctx->db->v.transaction_commit(ctx, callback, context);
++ return;
++ }
++
++ struct sql_commit_result_delayed *cb = i_new(struct sql_commit_result_delayed, 1);
++ const char *error = NULL;
++ ctx->db->v.transaction_commit_s(ctx, &error);
++ cb->db = db;
++ cb->error = i_strdup(error);
++ cb->callback = callback;
++ cb->context = context;
++ cb->to = timeout_add_short(0, sql_commit_delayed_callback, cb);
++ DLLIST_PREPEND(&db->commit_delayed_list, cb);
+ }
+
+ int sql_transaction_commit_s(struct sql_transaction_context **_ctx,
+@@ -900,6 +992,7 @@ void sql_wait(struct sql_db *db)
+ {
+ if (db->v.wait != NULL)
+ db->v.wait(db);
++ sql_call_delayed_callbacks(db);
+ }
+
+
+Index: trixie/src/lib-sql/sql-api.h
+===================================================================
+--- trixie.orig/src/lib-sql/sql-api.h
++++ trixie/src/lib-sql/sql-api.h
+@@ -123,7 +123,7 @@ const char *sql_escape_blob(struct sql_d
+ void sql_exec(struct sql_db *db, const char *query);
+ /* Execute SQL query and return result in callback. If fields list is given,
+ the returned fields are validated to be of correct type, and you can use
+- sql_result_next_row_get() */
++ sql_result_next_row_get(). The callback is never called immediately. */
+ void sql_query(struct sql_db *db, const char *query,
+ sql_query_callback_t *callback, void *context);
+ #define sql_query(db, query, callback, context) \
+@@ -229,7 +229,7 @@ struct sql_transaction_context *sql_tran
+ /* Don't require transaction to be atomic. Currently this is implemented only
+ with Cassandra to use UNLOGGED BATCH operations. */
+ void sql_transaction_set_non_atomic(struct sql_transaction_context *ctx);
+-/* Commit transaction. */
++/* Commit transaction. The callback is never called immediately. */
+ void sql_transaction_commit(struct sql_transaction_context **ctx,
+ sql_commit_callback_t *callback, void *context);
+ #define sql_transaction_commit(ctx, callback, context) \
--- /dev/null
+From ca82388f330be27e015ad4b3c08b0156307cf369 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 23 Jun 2026 14:59:45 +0000
+Subject: [PATCH 1/2] lib-storage: Allow NULL path in mailbox_list_mkdir_root()
+
+When path is NULL, look up the root directory path of the given type via
+mailbox_list_get_root_path() and create that. If there is no path of the
+requested type, nothing is done. This avoids open-coding the common
+get_root_path() + mkdir_root() pair at call sites.
+---
+ src/lib-storage/mailbox-list.c | 4 ++++
+ src/lib-storage/mailbox-list.h | 4 +++-
+ 2 files changed, 7 insertions(+), 1 deletion(-)
+
+Index: trixie/src/lib-storage/mailbox-list.c
+===================================================================
+--- trixie.orig/src/lib-storage/mailbox-list.c
++++ trixie/src/lib-storage/mailbox-list.c
+@@ -1019,6 +1019,10 @@ int mailbox_list_mkdir_root(struct mailb
+ {
+ const char *error;
+
++ if (path == NULL &&
++ !mailbox_list_get_root_path(list, type, &path))
++ return 0;
++
+ if (mailbox_list_try_mkdir_root(list, path, type, &error) < 0) {
+ mailbox_list_set_critical(list, "%s", error);
+ return -1;
+Index: trixie/src/lib-storage/mailbox-list.h
+===================================================================
+--- trixie.orig/src/lib-storage/mailbox-list.h
++++ trixie/src/lib-storage/mailbox-list.h
+@@ -177,7 +177,9 @@ void mailbox_list_get_permissions(struct
+ void mailbox_list_get_root_permissions(struct mailbox_list *list,
+ struct mailbox_permissions *permissions_r);
+ /* mkdir() a root directory of given type with proper permissions. The path can
+- be either the root itself or point to a directory under the root. */
++ be either the root itself or point to a directory under the root. If path is
++ NULL, the root path of the given type is looked up automatically; if there is
++ no such path, nothing is done. */
+ int mailbox_list_mkdir_root(struct mailbox_list *list, const char *path,
+ enum mailbox_list_path_type type);
+ /* Like mailbox_list_mkdir_root(), but don't log an error if it fails. */
--- /dev/null
+From 7774d50c731fa97e445fd9f4cb9e4f08e4415f8b Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 13 Apr 2026 17:28:43 +0200
+Subject: [PATCH 1/3] lib-storage: thread - Avoid excessive data stack growth
+ with many References msgids
+
+---
+ src/lib-storage/index/index-thread.c | 22 +++++++++++++++-------
+ 1 file changed, 15 insertions(+), 7 deletions(-)
+
+diff --git a/src/lib-storage/index/index-thread.c b/src/lib-storage/index/index-thread.c
+index 2b1ba045ac..e20a97072c 100644
+--- a/src/lib-storage/index/index-thread.c
++++ b/src/lib-storage/index/index-thread.c
+@@ -93,9 +93,11 @@ mail_strmap_rec_get_msgid(struct mail_thread_context *ctx,
+
+ /* get the nth message-id */
+ msgid = message_id_get_next(&msgids);
+- if (msgid != NULL) {
+- for (; n > 0; n--)
++ if (msgid != NULL && n > 0) {
++ for (; n > 1; n--) T_BEGIN {
+ msgid = message_id_get_next(&msgids);
++ } T_END;
++ msgid = message_id_get_next(&msgids);
+ }
+
+ if (msgid == NULL) {
+@@ -266,12 +268,18 @@ mail_thread_map_add_mail(struct mail_thread_context *ctx, struct mail *mail)
+ msgid = message_id_get_next(&references);
+ if (msgid != NULL) {
+ ref_index = MAIL_THREAD_NODE_REF_REFERENCES1;
++ mail_index_strmap_view_sync_add(ctx->strmap_sync,
++ mail->uid, ref_index, msgid);
+ do {
+- mail_index_strmap_view_sync_add(ctx->strmap_sync,
+- mail->uid,
+- ref_index, msgid);
+- ref_index++;
+- msgid = message_id_get_next(&references);
++ T_BEGIN {
++ ref_index++;
++ msgid = message_id_get_next(&references);
++ if (msgid != NULL) {
++ mail_index_strmap_view_sync_add(
++ ctx->strmap_sync, mail->uid,
++ ref_index, msgid);
++ }
++ } T_END;
+ } while (msgid != NULL);
+ } else {
+ /* no References:, use In-Reply-To: */
+--
+2.47.3
+
--- /dev/null
+From 4677492d038f5187363790b5a93bd5fccc989a98 Mon Sep 17 00:00:00 2001
+From: Stephan Bosch <stephan.bosch@open-xchange.com>
+Date: Fri, 25 Jul 2025 03:31:44 +0200
+Subject: [PATCH] lib-test: Add test_assert_memcmp*()
+
+---
+ src/lib-test/test-common.c | 33 +++++++++++++++++++++++++++++++++
+ src/lib-test/test-common.h | 25 ++++++++++++++++++++++++-
+ 2 files changed, 57 insertions(+), 1 deletion(-)
+
+Index: trixie/src/lib-test/test-common.c
+===================================================================
+--- trixie.orig/src/lib-test/test-common.c
++++ trixie/src/lib-test/test-common.c
+@@ -84,6 +84,39 @@ void test_assert_failed_strcmp_idx(const
+ #endif
+ }
+
++static void print_memcmp_data(const void *_data, size_t len)
++{
++ const unsigned char *data = _data;
++ size_t i;
++
++ for (i = 0; i < len; i++) {
++ if (data[i] >= 0x20 && data[i] < 0x7f)
++ printf("%c", data[i]);
++ else
++ printf("\\x%02X", data[i]);
++ }
++}
++
++void test_assert_failed_memcmp_idx(const char *code, const char *file, unsigned int line,
++ const void *src, const void *dst, size_t len, long long i)
++{
++ printf("%s:%u: Assert", file, line);
++ if (i == LLONG_MIN)
++ printf(" failed: %s\n", code);
++ else
++ printf("(#%lld) failed: %s\n", i, code);
++ printf(" \"");
++ print_memcmp_data(src, len);
++ printf("\" != \"");
++ print_memcmp_data(dst, len);
++ printf("\" (len == %zu)", len);
++ fflush(stdout);
++ test_success = FALSE;
++#ifdef STATIC_CHECKER
++ i_unreached();
++#endif
++}
++
+ void test_assert_failed_cmp_intmax_idx(const char *code, const char *file,
+ unsigned int line,
+ intmax_t src, intmax_t dst,
+Index: trixie/src/lib-test/test-common.h
+===================================================================
+--- trixie.orig/src/lib-test/test-common.h
++++ trixie/src/lib-test/test-common.h
+@@ -40,7 +40,14 @@ void test_begin(const char *name);
+ test_assert_strcmp_idx(s1, s2, LLONG_MIN); \
+ } STMT_END
+
+-/* Same as test_assert_strcmp expect that it takes an additional i as input.
++/* Additional parameters are m1 (source) and m2 (destination) memory and len
++ * in memcmp().
++ */
++#define test_assert_memcmp(m1, m2, len) STMT_START { \
++ test_assert_memcmp_idx(m1, m2, len, LLONG_MIN); \
++ } STMT_END
++
++/* Same as test_assert_strcmp except that it takes an additional i as input.
+ * When i is greater than or equals 0 it is used to identify the barrage of
+ * tests failed like in test_assert_idx.
+ */
+@@ -52,6 +59,19 @@ void test_begin(const char *name);
+ __FILE__, __LINE__, _temp_s1, _temp_s2, i); \
+ } STMT_END
+
++/* Same as test_assert_memcmp except that it takes an additional i as input.
++ * When i is greater than or equals 0 it is used to identify the barrage of
++ * tests failed like in test_assert_idx.
++*/
++#define test_assert_memcmp_idx(_m1, _m2, _len, i) STMT_START { \
++ const void *_temp_m1 = (_m1); \
++ const void *_temp_m2 = (_m2); \
++ const size_t _temp_len = (_len); \
++ if ((memcmp(_temp_m1,_temp_m2, _temp_len) != 0)) \
++ test_assert_failed_memcmp_idx("memcmp(" #_m1 "," #_m2 "," #_len ")", \
++ __FILE__, __LINE__, _temp_m1, _temp_m2, _temp_len, i); \
++ } STMT_END
++
+ #define test_assert_cmp_bool(_bool_value1, _op, _value2) \
+ test_assert_cmp((unsigned int) _bool_value1, _op, (unsigned int _bool_value2))
+
+@@ -92,6 +112,9 @@ void test_assert_failed_idx(const char *
+ void test_assert_failed_strcmp_idx(const char *code, const char *file, unsigned int line,
+ const char * src, const char * dst, long long i)
+ ATTR_STATIC_CHECKER_NORETURN;
++void test_assert_failed_memcmp_idx(const char *code, const char *file, unsigned int line,
++ const void *src, const void *dst, size_t len, long long i)
++ ATTR_STATIC_CHECKER_NORETURN;
+ void test_assert_failed_cmp_intmax_idx(const char *code, const char *file,
+ unsigned int line,
+ intmax_t src, intmax_t dst,
--- /dev/null
+From 8f04979a7e72d3b3e04f3a93bd8088f5e2332dd9 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Tue, 27 Jan 2026 19:02:30 +0200
+Subject: [PATCH] lib-test: Require both lengths in test_assert_memcmp()
+
+---
+ src/lib-sql/test-sql-sqlite.c | 2 +-
+ src/lib-test/test-common.h | 14 ++++++++------
+ src/lib/test-unicode-nf.c | 16 ++++++++--------
+ 3 files changed, 17 insertions(+), 15 deletions(-)
+
+Index: trixie/src/lib-test/test-common.h
+===================================================================
+--- trixie.orig/src/lib-test/test-common.h
++++ trixie/src/lib-test/test-common.h
+@@ -43,8 +43,8 @@ void test_begin(const char *name);
+ /* Additional parameters are m1 (source) and m2 (destination) memory and len
+ * in memcmp().
+ */
+-#define test_assert_memcmp(m1, m2, len) STMT_START { \
+- test_assert_memcmp_idx(m1, m2, len, LLONG_MIN); \
++#define test_assert_memcmp(m1, len1, m2, len2) STMT_START { \
++ test_assert_memcmp_idx(m1, len1, m2, len2, LLONG_MIN); \
+ } STMT_END
+
+ /* Same as test_assert_strcmp except that it takes an additional i as input.
+@@ -63,12 +63,14 @@ void test_begin(const char *name);
+ * When i is greater than or equals 0 it is used to identify the barrage of
+ * tests failed like in test_assert_idx.
+ */
+-#define test_assert_memcmp_idx(_m1, _m2, _len, i) STMT_START { \
++#define test_assert_memcmp_idx(_m1, _len1, _m2, _len2, i) STMT_START { \
+ const void *_temp_m1 = (_m1); \
+ const void *_temp_m2 = (_m2); \
+- const size_t _temp_len = (_len); \
+- if ((memcmp(_temp_m1,_temp_m2, _temp_len) != 0)) \
+- test_assert_failed_memcmp_idx("memcmp(" #_m1 "," #_m2 "," #_len ")", \
++ const size_t _temp_len = I_MIN((_len1), (_len2)); \
++ if ((_len1) != (_len2)) \
++ test_assert_failed_ucmp_intmax_idx(#_len1 " == " #_len2, __FILE__, __LINE__, _len1, _len2, "=", i); \
++ if ((memcmp(_temp_m1, _temp_m2, _temp_len) != 0)) \
++ test_assert_failed_memcmp_idx("memcmp(" #_m1 "," #_m2 "," #_len2 ")", \
+ __FILE__, __LINE__, _temp_m1, _temp_m2, _temp_len, i); \
+ } STMT_END
+
--- /dev/null
+From 45406e13f1faddf341922ff73962820bc0a99015 Mon Sep 17 00:00:00 2001
+From: Noah Meyerhans <noahm@debian.org>
+Date: Tue, 1 Sep 2026 21:12:51 -0400
+Subject: [PATCH] lib: xxh64: fix byte ordering bug on big-endian systems
+
+Convert from little-endian to host byte order where necessary.
+
+Background at https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1146449
+---
+ src/lib/xxh64.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/src/lib/xxh64.c b/src/lib/xxh64.c
+index 1c17833afd..4a151bd0c2 100644
+--- a/src/lib/xxh64.c
++++ b/src/lib/xxh64.c
+@@ -8,6 +8,8 @@
+ #include "lib.h"
+ #include "xxh64.h"
+
++#include <endian.h>
++
+ #define XXH64_PRIME1 UINT64_C(0x9E3779B185EBCA87)
+ #define XXH64_PRIME2 UINT64_C(0xC2B2AE3D27D4EB4F)
+ #define XXH64_PRIME3 UINT64_C(0x165667B19E3779F9)
+@@ -20,14 +22,14 @@ static inline uint64_t xxh64_read64(const void *p)
+ {
+ uint64_t v;
+ memcpy(&v, p, sizeof(v));
+- return v;
++ return le64toh(v); /* Convert from little-endian to host byte order */
+ }
+
+ static inline uint32_t xxh64_read32(const void *p)
+ {
+ uint32_t v;
+ memcpy(&v, p, sizeof(v));
+- return v;
++ return le32toh(v); /* Convert from little-endian to host byte order */
+ }
+
+ static uint64_t ATTR_UNSIGNED_WRAPS xxh64_round(uint64_t acc, uint64_t input)
+--
+2.47.3
+
--- /dev/null
+From 3eed5e02c49c2dc1522c038869f723e7c6d93ffc Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 18 Jul 2025 14:28:47 +0300
+Subject: [PATCH] *-login: Add LOGIN_PROXY_FAILURE_TYPE_AUTH_NOT_REPLIED
+
+---
+ src/imap-login/imap-proxy.c | 1 +
+ src/login-common/client-common.c | 1 +
+ src/login-common/login-proxy.c | 2 ++
+ src/login-common/login-proxy.h | 3 +++
+ src/pop3-login/pop3-proxy.c | 1 +
+ src/submission-login/submission-proxy.c | 1 +
+ 6 files changed, 9 insertions(+)
+
+diff --git a/src/imap-login/imap-proxy.c b/src/imap-login/imap-proxy.c
+index 69680ea752..03dad690e1 100644
+--- a/src/imap-login/imap-proxy.c
++++ b/src/imap-login/imap-proxy.c
+@@ -586,6 +586,7 @@ imap_proxy_send_failure_reply(struct imap_client *imap_client,
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG:
+ case LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_NOT_REPLIED:
+ client_send_reply_code(&imap_client->common, IMAP_CMD_REPLY_NO,
+ IMAP_RESP_CODE_SERVERBUG,
+ LOGIN_PROXY_FAILURE_MSG);
+diff --git a/src/login-common/client-common.c b/src/login-common/client-common.c
+index 0c11a925f1..eeb9d6da05 100644
+--- a/src/login-common/client-common.c
++++ b/src/login-common/client-common.c
+@@ -1386,6 +1386,7 @@ bool client_get_extra_disconnect_reason(struct client *client,
+ last_reason = "protocol failure";
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_NOT_REPLIED:
+ event_reason = "auth_failed";
+ last_reason = "authentication failure";
+ break;
+diff --git a/src/login-common/login-proxy.c b/src/login-common/login-proxy.c
+index 0e3e8c9be2..10c07b711d 100644
+--- a/src/login-common/login-proxy.c
++++ b/src/login-common/login-proxy.c
+@@ -830,6 +830,7 @@ bool login_proxy_failed(struct login_proxy *proxy, struct event *event,
+ log_prefix = "Remote server sent invalid input: ";
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_NOT_REPLIED:
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED:
+ log_prefix = "";
+ try_reconnect = FALSE;
+@@ -857,6 +858,7 @@ bool login_proxy_failed(struct login_proxy *proxy, struct event *event,
+
+ if (type != LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED &&
+ type != LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED &&
++ type != LOGIN_PROXY_FAILURE_TYPE_AUTH_NOT_REPLIED &&
+ type != LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL)
+ e_error(event, "%s%s", log_prefix, reason);
+ else if (proxy->client->set->auth_verbose)
+diff --git a/src/login-common/login-proxy.h b/src/login-common/login-proxy.h
+index 65895f3d75..736c2eb586 100644
+--- a/src/login-common/login-proxy.h
++++ b/src/login-common/login-proxy.h
+@@ -35,6 +35,9 @@ enum login_proxy_failure_type {
+ /* Authentication failed to backend. The LOGIN/AUTH command reply was
+ already sent to the client. */
+ LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED,
++ /* Authentication failed to backend. The LOGIN/AUTH command reply was
++ NOT sent to the client yet. */
++ LOGIN_PROXY_FAILURE_TYPE_AUTH_NOT_REPLIED,
+ /* Authentication failed with a temporary failure code. Attempting it
+ again might work. */
+ LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL,
+diff --git a/src/pop3-login/pop3-proxy.c b/src/pop3-login/pop3-proxy.c
+index f8010d3d12..ad7786903b 100644
+--- a/src/pop3-login/pop3-proxy.c
++++ b/src/pop3-login/pop3-proxy.c
+@@ -368,6 +368,7 @@ pop3_proxy_send_failure_reply(struct client *client,
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG:
+ case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_NOT_REPLIED:
+ client_send_reply(client, POP3_CMD_REPLY_ERROR,
+ LOGIN_PROXY_FAILURE_MSG);
+ break;
+diff --git a/src/submission-login/submission-proxy.c b/src/submission-login/submission-proxy.c
+index 6f2c7f436a..f86845dcbc 100644
+--- a/src/submission-login/submission-proxy.c
++++ b/src/submission-login/submission-proxy.c
+@@ -752,6 +752,7 @@ submission_proxy_send_failure_reply(struct submission_client *subm_client,
+ case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG:
+ case LOGIN_PROXY_FAILURE_TYPE_PROTOCOL:
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REDIRECT:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_NOT_REPLIED:
+ i_assert(cmd != NULL);
+ subm_client->pending_auth = NULL;
+ smtp_server_reply(cmd, 454, "4.7.0", LOGIN_PROXY_FAILURE_MSG);
+--
+2.47.3
+
--- /dev/null
+From 7c874f2d6461e80ff8f7111fa36a7551747129f1 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Sun, 3 May 2026 16:20:00 +0000
+Subject: [PATCH 1/2] login-common: Fix crash when XCLIENT FORWARD= base64
+ contains NUL byte
+
+Sending "XCLIENT FORWARD=AA==" (base64 for a single NUL byte) followed
+by a regular login crashed pop3-login (and other login services) with:
+
+ Panic: file ../../src/lib/array.h: line 275 (array_idx_i):
+ assertion failed: (idx < array->buffer->used / array->element_size)
+
+p_strsplit_tabescaped() truncates at the first NUL, so an all-NUL
+payload produced an empty fields list. forward_fields was still
+created (but empty), and the later array_front() call in
+sasl_server_auth_begin() then panicked on the empty array.
+
+Reject empty payloads and payloads containing NUL bytes during base64
+decode, so the array is never created in this case.
+---
+ src/login-common/client-common.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/src/login-common/client-common.c b/src/login-common/client-common.c
+index eeb9d6da05..00e4f9abbf 100644
+--- a/src/login-common/client-common.c
++++ b/src/login-common/client-common.c
+@@ -988,6 +988,11 @@ bool client_forward_decode_base64(struct client *client, const char *value)
+ string_t *str = t_str_new(MAX_BASE64_DECODED_SIZE(value_len));
+ if (base64_decode(value, value_len, str) < 0)
+ return FALSE;
++ /* Embedded NUL would yield a created-but-empty array, panicking later
++ in array_front() during sasl_server_auth_begin(). */
++ if (str_len(str) == 0 ||
++ memchr(str_data(str), '\0', str_len(str)) != NULL)
++ return FALSE;
+
+ char **_fields = p_strsplit_tabescaped(client->preproxy_pool,
+ str_c(str));
+--
+2.47.3
+
--- /dev/null
+From 275331da2aca21fb75483f226e72b0890d43ede4 Mon Sep 17 00:00:00 2001
+From: Stephan Bosch <stephan.bosch@open-xchange.com>
+Date: Fri, 28 Nov 2025 03:32:36 +0100
+Subject: [PATCH 1/5] login-common: client-common - Add client_disconnect()
+ vfunc
+
+---
+ src/login-common/client-common.c | 2 ++
+ src/login-common/client-common.h | 1 +
+ 2 files changed, 3 insertions(+)
+
+Index: trixie/src/login-common/client-common.c
+===================================================================
+--- trixie.orig/src/login-common/client-common.c
++++ trixie/src/login-common/client-common.c
+@@ -433,6 +433,8 @@ void client_disconnect(struct client *cl
+ if (!client->login_success) {
+ bool unref = FALSE;
+
++ if (client->v.disconnect != NULL)
++ client->v.disconnect(client, reason);
+ io_remove(&client->io);
+ ssl_iostream_destroy(&client->ssl_iostream);
+ if (client->iostream_fd_proxy != NULL) {
+Index: trixie/src/login-common/client-common.h
+===================================================================
+--- trixie.orig/src/login-common/client-common.h
++++ trixie/src/login-common/client-common.h
+@@ -122,6 +122,7 @@ struct client_auth_reply {
+ struct client_vfuncs {
+ struct client *(*alloc)(pool_t pool);
+ int (*create)(struct client *client);
++ void (*disconnect)(struct client *client, const char *reason);
+ void (*destroy)(struct client *client);
+ int (*reload_config)(struct client *client, const char **error_r);
+ void (*notify_auth_ready)(struct client *client);
--- /dev/null
+From db0835c84bf754a260ce0d3dca1d69eb90f87518 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 3 Dec 2025 12:06:42 +0200
+Subject: [PATCH] login-common, *-login: Add proxy_dest_connection_limit
+ error_code to proxy_session_finished
+
+If IMAP backend returns with [LIMIT] or POP3 backend returns with [IN-USE],
+use this error code rather than the generic proxy_dest_auth_failed.
+Error messages are also updated.
+---
+ src/imap-login/imap-proxy.c | 9 +++++++++
+ src/login-common/client-common.c | 4 ++++
+ src/login-common/login-proxy.c | 2 ++
+ src/login-common/login-proxy.h | 3 +++
+ src/pop3-login/pop3-proxy.c | 3 +++
+ src/submission-login/submission-proxy.c | 1 +
+ 6 files changed, 22 insertions(+)
+
+Index: dovecot/src/imap-login/imap-proxy.c
+===================================================================
+--- dovecot.orig/src/imap-login/imap-proxy.c
++++ dovecot/src/imap-login/imap-proxy.c
+@@ -284,6 +284,12 @@ static bool auth_resp_code_is_serverbug(
+ strlen(IMAP_RESP_CODE_SERVERBUG"]")) == 0;
+ }
+
++static bool auth_resp_code_is_limit(const char *resp)
++{
++ return strncasecmp(resp, IMAP_RESP_CODE_LIMIT"]",
++ strlen(IMAP_RESP_CODE_LIMIT"]")) == 0;
++}
++
+ static bool
+ auth_resp_code_parse_referral(struct client *client, const char *resp,
+ const char **userhostport_r)
+@@ -448,6 +454,8 @@ int imap_proxy_parse_line(struct client
+ else if (auth_resp_code_is_serverbug(line + 4))
+ failure_type = LOGIN_PROXY_FAILURE_TYPE_REMOTE;
+ else {
++ if (auth_resp_code_is_limit(line + 4))
++ failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED;
+ client_send_raw(client, t_strconcat(
+ imap_client->cmd_tag, " ", line, "\r\n", NULL));
+ }
+@@ -587,6 +595,7 @@ imap_proxy_send_failure_reply(struct ima
+ imap_client->cmd_tag, " NO ", reason, "\r\n", NULL));
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED:
+ /* reply was already sent */
+ break;
+ }
+Index: dovecot/src/login-common/client-common.c
+===================================================================
+--- dovecot.orig/src/login-common/client-common.c
++++ dovecot/src/login-common/client-common.c
+@@ -1397,6 +1397,10 @@ bool client_get_extra_disconnect_reason(
+ event_reason = "redirected";
+ last_reason = "redirected";
+ break;
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED:
++ event_reason = "connection_limit";
++ last_reason = "connection limit reached";
++ break;
+ default:
+ i_unreached();
+ }
+Index: dovecot/src/login-common/login-proxy.c
+===================================================================
+--- dovecot.orig/src/login-common/login-proxy.c
++++ dovecot/src/login-common/login-proxy.c
+@@ -830,6 +830,7 @@ bool login_proxy_failed(struct login_pro
+ log_prefix = "Remote server sent invalid input: ";
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED:
+ log_prefix = "";
+ try_reconnect = FALSE;
+ break;
+@@ -855,6 +856,7 @@ bool login_proxy_failed(struct login_pro
+ }
+
+ if (type != LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED &&
++ type != LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED &&
+ type != LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL)
+ e_error(event, "%s%s", log_prefix, reason);
+ else if (proxy->client->set->auth_verbose)
+Index: dovecot/src/login-common/login-proxy.h
+===================================================================
+--- dovecot.orig/src/login-common/login-proxy.h
++++ dovecot/src/login-common/login-proxy.h
+@@ -41,6 +41,9 @@ enum login_proxy_failure_type {
+ /* Authentication requests connecting to another host. The reason
+ string contains the host (and optionally :port). */
+ LOGIN_PROXY_FAILURE_TYPE_AUTH_REDIRECT,
++ /* Authentication failed because user has reached some limit.
++ The LOGIN/AUTH command reply was already sent to the client. */
++ LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED,
+ };
+
+ struct login_proxy_settings {
+Index: dovecot/src/pop3-login/pop3-proxy.c
+===================================================================
+--- dovecot.orig/src/pop3-login/pop3-proxy.c
++++ dovecot/src/pop3-login/pop3-proxy.c
+@@ -333,6 +333,8 @@ int pop3_proxy_parse_line(struct client
+ } else if (pop3_proxy_parse_referral(client, line + 5, &line)) {
+ failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH_REDIRECT;
+ } else {
++ if (str_begins_with(line, "-ERR [IN-USE]"))
++ failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED;
+ client_send_raw(client, t_strconcat(line, "\r\n", NULL));
+ line += 5;
+ }
+@@ -374,6 +376,7 @@ pop3_proxy_send_failure_reply(struct cli
+ client_send_reply(client, POP3_CMD_REPLY_ERROR, reason);
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED:
+ /* reply was already sent */
+ break;
+ }
+Index: dovecot/src/submission-login/submission-proxy.c
+===================================================================
+--- dovecot.orig/src/submission-login/submission-proxy.c
++++ dovecot/src/submission-login/submission-proxy.c
+@@ -750,6 +750,7 @@ submission_proxy_send_failure_reply(stru
+ smtp_server_reply_submit(subm_client->proxy_reply);
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED:
+ /* reply was already sent */
+ i_assert(cmd == NULL);
+ break;
--- /dev/null
+Index: dovecot/pigeonhole/src/managesieve-login/managesieve-proxy.c
+===================================================================
+--- dovecot.orig/pigeonhole/src/managesieve-login/managesieve-proxy.c
++++ dovecot/pigeonhole/src/managesieve-login/managesieve-proxy.c
+@@ -658,7 +658,7 @@ int managesieve_proxy_parse_line(struct
+ &reason))
+ failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH_REDIRECT;
+ else {
+- failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH;
++ failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED;
+ client_send_no(client, AUTH_FAILED_MSG);
+ }
+
+@@ -702,6 +702,7 @@ managesieve_proxy_send_failure_reply(str
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG:
+ case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_NOT_REPLIED:
+ client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_NO,
+ NULL, LOGIN_PROXY_FAILURE_MSG);
+ break;
+@@ -709,7 +710,7 @@ managesieve_proxy_send_failure_reply(str
+ client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_NO,
+ "TRYLATER", reason);
+ break;
+- case LOGIN_PROXY_FAILURE_TYPE_AUTH:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
+ /* reply was already sent */
+ break;
+ }
--- /dev/null
+Index: dovecot/pigeonhole/src/managesieve-login/managesieve-proxy.c
+===================================================================
+--- dovecot.orig/pigeonhole/src/managesieve-login/managesieve-proxy.c
++++ dovecot/pigeonhole/src/managesieve-login/managesieve-proxy.c
+@@ -711,6 +711,7 @@ managesieve_proxy_send_failure_reply(str
+ "TRYLATER", reason);
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED:
++ case LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED:
+ /* reply was already sent */
+ break;
+ }
--- /dev/null
+From 53abb9f06e9d81ea41e30c6552d0ff0047e41e9d Mon Sep 17 00:00:00 2001
+From: Markus Valentin <markus.valentin@open-xchange.com>
+Date: Thu, 23 Apr 2026 12:53:07 +0200
+Subject: [PATCH 1/2] submission: Fix settings leak on error paths in
+ client_create_from_input()
+
+---
+ src/submission/main.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/submission/main.c b/src/submission/main.c
+index 00e6642510..3b110f4f4a 100644
+--- a/src/submission/main.c
++++ b/src/submission/main.c
+@@ -208,6 +208,7 @@ client_create_from_input(const struct mail_storage_service_input *input,
+ "(submission_relay_host is unset)";
+ send_error(fd_out, event, set->hostname,
+ "4.3.5", MAIL_ERRSTR_CRITICAL_MSG);
++ settings_free(set);
+ mail_user_deinit(&mail_user);
+ event_unref(&event);
+ return -1;
+@@ -216,6 +217,7 @@ client_create_from_input(const struct mail_storage_service_input *input,
+ if (ret < 0) {
+ send_error(fd_out, event, my_hostname,
+ "4.7.0", MAIL_ERRSTR_CRITICAL_MSG);
++ settings_free(set);
+ mail_user_deinit(&mail_user);
+ event_unref(&event);
+ return -1;
+--
+2.47.3
+
--- /dev/null
+From 06af53902479572fc96f04b4372fdabb9d01996b Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Fri, 9 Jan 2026 13:31:42 +0200
+Subject: [PATCH] trash: Use mailbox event in trash_try_mailbox() for settings
+Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1127029
+
+---
+ src/plugins/trash/trash-plugin.c | 18 ++++++++++--------
+ 1 file changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/src/plugins/trash/trash-plugin.c b/src/plugins/trash/trash-plugin.c
+index 827ba91ec5..7865de2ef6 100644
+--- a/src/plugins/trash/trash-plugin.c
++++ b/src/plugins/trash/trash-plugin.c
+@@ -570,14 +570,13 @@ static int trash_mailbox_priority_cmp(const struct trash_mailbox *t1,
+ return strcmp(t1->name, t2->name);
+ }
+
+-static int trash_try_mailbox(struct mail_namespace *ns, const char *box_name,
++static int trash_try_mailbox(struct mail_namespace *ns, struct event *mbox_event,
+ const char **error_r)
+ {
+ struct trash_user *tuser = TRASH_USER_CONTEXT_REQUIRE(ns->user);
+ const struct trash_settings *trash_set;
+- if (settings_try_get_filter(ns->list->event, "mailbox", box_name,
+- &trash_setting_parser_info, 0,
+- &trash_set, error_r) < 0)
++ if (settings_get(mbox_event, &trash_setting_parser_info, 0,
++ &trash_set, error_r) < 0)
+ return -1;
+ unsigned int trash_priority = trash_set->trash_priority;
+ settings_free(trash_set);
+@@ -586,9 +585,8 @@ static int trash_try_mailbox(struct mail_namespace *ns, const char *box_name,
+ return 0;
+
+ const struct mailbox_settings *box_set;
+- if (settings_try_get_filter(ns->list->event, "mailbox", box_name,
+- &mailbox_setting_parser_info, 0,
+- &box_set, error_r) < 0)
++ if (settings_get(mbox_event, &mailbox_setting_parser_info, 0,
++ &box_set, error_r) < 0)
+ return -1;
+
+ const char *vname =
+@@ -618,10 +616,14 @@ static int trash_find_mailboxes(struct mail_user *user)
+ continue;
+
+ array_foreach_elem(&ns->set->mailboxes, box_name) {
+- if (trash_try_mailbox(ns, box_name, &error) < 0) {
++ struct event *mbox_event =
++ mail_storage_mailbox_create_event(ns->list->event, ns->list, box_name);
++ if (trash_try_mailbox(ns, mbox_event, &error) < 0) {
+ user->error = p_strdup(user->pool, error);
++ event_unref(&mbox_event);
+ return -1;
+ }
++ event_unref(&mbox_event);
+ }
+ }
+
+--
+2.47.3
+
--- /dev/null
+From ed04e392f97a523dd7bc75161af280df6cf3ce6f Mon Sep 17 00:00:00 2001
+From: Markus Valentin <markus.valentin@open-xchange.com>
+Date: Thu, 23 Apr 2026 13:07:08 +0200
+Subject: [PATCH 2/2] auth: Fix prefixing forward_fields without a value from
+ client
+
+Bare tokens (without '=') were not prefixed, only key=value pairs were.
+In practice this affected forward_fields, where a bare token such as
+'nopassword' would land in extra_fields unprefixed instead of as
+'forward_nopassword', allowing injection of internal auth control fields.
+---
+ src/auth/auth-fields.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/auth/auth-fields.c b/src/auth/auth-fields.c
+index 8b92836588..6e1ccbbcab 100644
+--- a/src/auth/auth-fields.c
++++ b/src/auth/auth-fields.c
+@@ -125,7 +125,7 @@ static void auth_fields_import_prefixed_args(struct auth_fields *fields,
+ for (; *args != NULL; args++) {
+ value = strchr(*args, '=');
+ if (value == NULL) {
+- key = *args;
++ key = *prefix != '\0' ? t_strconcat(prefix, *args, NULL) : *args;
+ } else {
+ key = t_strdup_until(*args, value++);
+ if (*prefix != '\0')
+--
+2.47.3
+
--- /dev/null
+From 5b894458088293ffaef32d028af81a70506b7f20 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Wed, 3 Jun 2026 12:56:20 +0000
+Subject: [PATCH 2/4] auth: db-oauth2: Fix scope check to require all
+ configured scopes
+
+Switch token-in-scope check to AND semantics: all configured scopes
+must be present in the token. Behaviour now matches the JWT path.
+---
+ src/auth/db-oauth2.c | 17 ++++++++++-------
+ 1 file changed, 10 insertions(+), 7 deletions(-)
+
+Index: trixie/src/auth/db-oauth2.c
+===================================================================
+--- trixie.orig/src/auth/db-oauth2.c
++++ trixie/src/auth/db-oauth2.c
+@@ -556,22 +556,25 @@ db_oauth2_token_in_scope(struct db_oauth
+ if (array_is_empty(&req->db->set->scope))
+ return TRUE;
+
+- bool found = FALSE;
+ const char *value = auth_fields_find(req->fields, "scope");
+ bool has_scope = value != NULL;
+ if (!has_scope)
+ value = auth_fields_find(req->fields, "aud");
+ e_debug(authdb_event(req->auth_request),
+- "Token scope(s): %s",
+- value);
+- if (value != NULL) {
+- const char *wanted_scope;
++ "Token scope(s): %s", value);
++
++ bool found = FALSE;
++ if (value != NULL && *value != '\0') {
+ const char *const *entries = has_scope ?
+ t_strsplit_spaces(value, " ") :
+ t_strsplit_tabescaped(value);
+- array_foreach_elem(&req->db->set->scope, wanted_scope) {
+- if ((found = str_array_find(entries, wanted_scope)))
++ const char *wanted;
++ found = TRUE;
++ array_foreach_elem(&req->db->set->scope, wanted) {
++ if (!str_array_find(entries, wanted)) {
++ found = FALSE;
+ break;
++ }
+ }
+ }
+ if (!found) {
--- /dev/null
+From 3c847240b1c0afc52ee570f45409f094c546a3a0 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 28 Apr 2026 01:51:01 +0300
+Subject: [PATCH 2/2] doveadm: Avoid leaking doveadm_password or
+ doveadm_api_key lengths
+
+---
+ src/doveadm/client-connection-http.c | 4 ++--
+ src/doveadm/client-connection-tcp.c | 2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/src/doveadm/client-connection-http.c b/src/doveadm/client-connection-http.c
+index dfbc3528d7..f083ac6e14 100644
+--- a/src/doveadm/client-connection-http.c
++++ b/src/doveadm/client-connection-http.c
+@@ -973,7 +973,7 @@ doveadm_http_server_auth_basic(struct client_request_http *req,
+ b64_value = t_base64_encode_str(0, UINT_MAX, value);
+
+ if (creds->data != NULL &&
+- str_equals_timing_almost_safe(str_c(b64_value), creds->data))
++ str_equals_hash_timing_safe(str_c(b64_value), creds->data))
+ return TRUE;
+
+ e_error(conn->conn.event,
+@@ -999,7 +999,7 @@ doveadm_http_server_auth_api_key(struct client_request_http *req,
+
+ b64_value = t_base64_encode_str(0, UINT_MAX, set->doveadm_api_key);
+ if (creds->data != NULL &&
+- str_equals_timing_almost_safe(creds->data, str_c(b64_value)))
++ str_equals_hash_timing_safe(creds->data, str_c(b64_value)))
+ return TRUE;
+
+ e_error(conn->conn.event,
+diff --git a/src/doveadm/client-connection-tcp.c b/src/doveadm/client-connection-tcp.c
+index cb16f7214b..b374028c97 100644
+--- a/src/doveadm/client-connection-tcp.c
++++ b/src/doveadm/client-connection-tcp.c
+@@ -400,7 +400,7 @@ client_connection_tcp_authenticate(struct client_connection_tcp *conn)
+ return -1;
+ }
+ pass = t_strndup(data + 9, size - 9);
+- if (!str_equals_timing_almost_safe(pass, set->doveadm_password)) {
++ if (!str_equals_hash_timing_safe(pass, set->doveadm_password)) {
+ e_error(conn->conn.event,
+ "doveadm client authenticated with wrong password");
+ return -1;
+--
+2.47.3
+
--- /dev/null
+From 4a731e5de4191134e27e14c62a42ae7201d26e26 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 6 Jul 2026 07:17:42 +0000
+Subject: [PATCH 2/3] doveadm: compress-connect - Don't hang when server
+ rejects COMPRESS
+
+After sending a COMPRESS command the client stopped reading stdin and
+waited for the server's reply, but it only resumed on an "OK Begin
+compression" reply. If the server rejected COMPRESS (e.g. a NO reply for
+an unsupported mechanism), compress_waiting was never cleared: the client
+waited forever for an OK that never came while the server waited for the
+next command, deadlocking the session.
+
+Remember the COMPRESS command tag and treat any tagged reply to it that
+isn't the compression-start reply as a rejection, resuming the pipelined
+input.
+---
+ src/doveadm/doveadm-compress.c | 34 +++++++++++++++++++++++++++++++++-
+ 1 file changed, 33 insertions(+), 1 deletion(-)
+
+Index: trixie/src/doveadm/doveadm-compress.c
+===================================================================
+--- trixie.orig/src/doveadm/doveadm-compress.c
++++ trixie/src/doveadm/doveadm-compress.c
+@@ -20,6 +20,7 @@ enum server_input_line_type {
+ SERVER_INPUT_LINE_TYPE_DEFAULT,
+ SERVER_INPUT_LINE_TYPE_STARTTLS,
+ SERVER_INPUT_LINE_TYPE_COMPRESS,
++ SERVER_INPUT_LINE_TYPE_COMPRESS_FAILED,
+ };
+
+ struct client {
+@@ -32,6 +33,7 @@ struct client {
+ struct ssl_iostream *ssl_iostream;
+ const struct compression_handler *handler;
+ char *algorithm;
++ char *compress_tag;
+ bool tls;
+ bool compressed;
+ bool compress_waiting;
+@@ -111,17 +113,23 @@ cmd_dump_imap_compress(struct doveadm_cm
+ static bool
+ client_input_get_compress_algorithm(struct client *client, const char *line)
+ {
+- const char *algorithm;
++ const char *algorithm, *tag = line;
++ size_t tag_len;
+
+ /* skip tag */
+ while (*line != ' ' && *line != '\0')
+ line++;
++ tag_len = line - tag;
+ if (!str_begins_icase(line, " COMPRESS ", &algorithm))
+ return FALSE;
+
+ if (compression_lookup_handler(t_str_lcase(algorithm),
+ &client->handler) <= 0)
+ i_fatal("Unsupported compression mechanism: %s", algorithm);
++ /* Remember the tag so we can tell whether the server accepted or
++ rejected this COMPRESS command. */
++ i_free(client->compress_tag);
++ client->compress_tag = i_strndup(tag, tag_len);
+ return TRUE;
+ }
+
+@@ -190,6 +198,13 @@ static bool server_input_is_compress_rep
+ return str_begins_with(line, " OK Begin compression");
+ }
+
++static bool server_input_is_reply_to_tag(const char *line, const char *tag)
++{
++ size_t tag_len = strlen(tag);
++
++ return strncmp(line, tag, tag_len) == 0 && line[tag_len] == ' ';
++}
++
+ static enum server_input_line_type
+ server_input_line_type(struct client *client, const char *line)
+ {
+@@ -197,6 +212,13 @@ server_input_line_type(struct client *cl
+ return SERVER_INPUT_LINE_TYPE_STARTTLS;
+ if (!client->compressed && server_input_is_compress_reply(line))
+ return SERVER_INPUT_LINE_TYPE_COMPRESS;
++ /* The server can reject COMPRESS (e.g. an unsupported mechanism). If we
++ are waiting for its reply, a tagged reply that isn't the "OK Begin
++ compression" above means the command failed - resume sending the
++ pipelined input instead of waiting forever. */
++ if (client->compress_waiting && client->compress_tag != NULL &&
++ server_input_is_reply_to_tag(line, client->compress_tag))
++ return SERVER_INPUT_LINE_TYPE_COMPRESS_FAILED;
+ return SERVER_INPUT_LINE_TYPE_DEFAULT;
+ }
+
+@@ -269,9 +291,18 @@ static void server_input(struct client *
+ client->output = output;
+ client->compressed = TRUE;
+ client->compress_waiting = FALSE;
++ i_free_and_null(client->compress_tag);
+ i_stream_set_input_pending(client->stdin_input, TRUE);
+ break;
+ }
++ case SERVER_INPUT_LINE_TYPE_COMPRESS_FAILED:
++ /* Server rejected COMPRESS. Resume sending input. */
++ e_info(client->event, "<Compression rejected>");
++ client->handler = NULL;
++ client->compress_waiting = FALSE;
++ i_free_and_null(client->compress_tag);
++ i_stream_set_input_pending(client->stdin_input, TRUE);
++ break;
+ case SERVER_INPUT_LINE_TYPE_DEFAULT:
+ break;
+ }
+@@ -329,6 +360,7 @@ static void cmd_compress_connect(struct
+ ssl_iostream_destroy(&client.ssl_iostream);
+ i_stream_unref(&client.stdin_input);
+ i_stream_unref(&client.input);
++ i_free(client.compress_tag);
+ o_stream_unref(&client.output);
+ event_unref(&client.event);
+ if (close(fd) < 0)
--- /dev/null
+From a8b185e828d1a67ab66efd5c4a7d689e5eef0025 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 24 Mar 2026 12:03:08 +0200
+Subject: [PATCH 2/2] dsync: Fix escaping mail or attribute value that begins
+ with a "." line
+
+Such a mail or attribute would have escaped the value parameter and the rest
+of the value would have been processed as dsync stream commands.
+---
+ src/doveadm/dsync/dsync-ibc-stream.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/doveadm/dsync/dsync-ibc-stream.c b/src/doveadm/dsync/dsync-ibc-stream.c
+index 0b3038201d..390c9a50c0 100644
+--- a/src/doveadm/dsync/dsync-ibc-stream.c
++++ b/src/doveadm/dsync/dsync-ibc-stream.c
+@@ -1533,7 +1533,7 @@ dsync_ibc_stream_send_mailbox_attribute(struct dsync_ibc *_ibc,
+ dsync_ibc_stream_send_string(ibc, str);
+
+ if (attr->value_stream != NULL) {
+- ibc->value_output_last = '\0';
++ ibc->value_output_last = '\n';
+ ibc->value_output = attr->value_stream;
+ i_stream_ref(ibc->value_output);
+ (void)dsync_ibc_stream_send_value_stream(ibc);
+@@ -1913,7 +1913,7 @@ dsync_ibc_stream_send_mail(struct dsync_ibc *_ibc,
+ dsync_ibc_stream_send_string(ibc, str);
+
+ if (mail->input != NULL) {
+- ibc->value_output_last = '\0';
++ ibc->value_output_last = '\n';
+ ibc->value_output = mail->input;
+ i_stream_ref(ibc->value_output);
+ (void)dsync_ibc_stream_send_value_stream(ibc);
+--
+2.47.3
+
--- /dev/null
+From d4fc8475f3b9cf8debbf9427d7bb8e9bbecb07ba Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 1 May 2026 17:40:17 +0000
+Subject: [PATCH 2/5] imap: Recreate multiplex ostream side channel after
+ unhibernation
+
+This will be needed by the following changes to send dict_reset commands via
+the side channel.
+---
+ src/imap/imap-state.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/src/imap/imap-state.c b/src/imap/imap-state.c
+index 3736296bea..fbd383d9de 100644
+--- a/src/imap/imap-state.c
++++ b/src/imap/imap-state.c
+@@ -723,6 +723,13 @@ import_state_compress(struct client *client, const unsigned char *data,
+ *error_r = t_strdup_printf("Unknown COMPRESS handler %s", name);
+ return 0;
+ }
++ /* Recreate the side channel that cmd-compress would have created in
++ the previous (now-exited) imap process. It is used to forward
++ dict_reset commands back to the imap-login proxy after every tagged
++ reply. */
++ if (client->multiplex_output != NULL &&
++ client->side_channel_output == NULL)
++ client_create_side_channel_output(client);
+ return p - data;
+ }
+
+--
+2.47.3
+
--- /dev/null
+From 480e9dfb6140f51992fdf83a9e9418e1dcf742d2 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 17 Apr 2026 16:55:05 +0200
+Subject: [PATCH 02/14] imap: Stream BODYSTRUCTURE to client without ostream
+ memory duplication
+
+For huge BODYSTRUCTURE responses (messages with many MIME parts),
+o_stream_send_str() caused the ostream to copy the entire string into
+its ring buffer when the stream was corked, doubling memory usage.
+
+Use the same o_stream_set_max_buffer_size(0) + o_stream_send_istream()
+pattern as fetch_stream_continue() so the ostream buffer stays at 0
+bytes and the I/O loop handles flow control via WAIT_OUTPUT.
+
+Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
+---
+ src/imap/imap-fetch.c | 37 +++++++++++++++++++++++++++++++++----
+ 1 file changed, 33 insertions(+), 4 deletions(-)
+
+diff --git a/src/imap/imap-fetch.c b/src/imap/imap-fetch.c
+index ca4b3216ce..b4a8a465d7 100644
+--- a/src/imap/imap-fetch.c
++++ b/src/imap/imap-fetch.c
+@@ -732,6 +732,34 @@ static bool fetch_body_init(struct imap_fetch_init_context *ctx)
+ return imap_fetch_body_section_init(ctx);
+ }
+
++static int fetch_bodystructure_stream_continue(struct imap_fetch_context *ctx)
++{
++ struct imap_fetch_state *state = &ctx->state;
++ enum ostream_send_istream_result res;
++
++ o_stream_set_max_buffer_size(ctx->client->output, 0);
++ res = o_stream_send_istream(ctx->client->output, state->cur_input);
++ o_stream_set_max_buffer_size(ctx->client->output, SIZE_MAX);
++
++ switch (res) {
++ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
++ i_stream_unref(&state->cur_input);
++ state->cont_handler = NULL;
++ if (o_stream_send(ctx->client->output, ")", 1) < 0)
++ return -1;
++ return 1;
++ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
++ i_unreached();
++ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
++ return 0;
++ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
++ i_unreached();
++ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
++ return -1;
++ }
++ i_unreached();
++}
++
+ static int fetch_bodystructure(struct imap_fetch_context *ctx,
+ struct mail *mail, void *context ATTR_UNUSED)
+ {
+@@ -748,12 +776,13 @@ static int fetch_bodystructure(struct imap_fetch_context *ctx,
+ return -1;
+ }
+
+- if (o_stream_send(ctx->client->output, "BODYSTRUCTURE (", 15) < 0 ||
+- o_stream_send_str(ctx->client->output, bodystructure) < 0 ||
+- o_stream_send(ctx->client->output, ")", 1) < 0)
++ if (o_stream_send(ctx->client->output, "BODYSTRUCTURE (", 15) < 0)
+ return -1;
+
+- return 1;
++ ctx->state.cur_input =
++ i_stream_create_from_data(bodystructure, strlen(bodystructure));
++ ctx->state.cont_handler = fetch_bodystructure_stream_continue;
++ return fetch_bodystructure_stream_continue(ctx);
+ }
+
+ static bool fetch_bodystructure_init(struct imap_fetch_init_context *ctx)
+--
+2.47.3
+
--- /dev/null
+From fc1898a575fd4cf4c2a4b3e00da630825c0acf60 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 22 Apr 2026 12:58:56 +0300
+Subject: [PATCH 2/2] imap-hibernate: Use imap-parser API for parsing DONE
+ command
+
+Replace the ad-hoc DONE/IDLE tokenizer with imap-parser.h: use
+imap_parser_read_tag() to read the tag and imap_parser_read_word() to
+read the DONE and IDLE keywords. This delegates bounds checks and
+character validation to the shared parser.
+---
+ src/imap-hibernate/imap-client.c | 103 ++++++++++++++++++-------------
+ 1 file changed, 59 insertions(+), 44 deletions(-)
+
+diff --git a/src/imap-hibernate/imap-client.c b/src/imap-hibernate/imap-client.c
+index 2110fe8e8e..564ef44f4f 100644
+--- a/src/imap-hibernate/imap-client.c
++++ b/src/imap-hibernate/imap-client.c
+@@ -21,6 +21,7 @@
+ #include "master-service-settings.h"
+ #include "imap-keepalive.h"
+ #include "imap-master-connection.h"
++#include "imap-parser.h"
+ #include "imap-client.h"
+
+ #include <unistd.h>
+@@ -334,64 +335,78 @@ static void imap_client_move_back(struct imap_client *client)
+ }
+
+ static enum imap_client_input_state
+-imap_client_input_parse(const unsigned char *data, size_t size, const char **tag_r)
++imap_client_input_parse_input(struct istream *input, struct imap_parser *parser,
++ const char **tag_r)
+ {
+- const unsigned char *tag_start, *tag_end;
+-
++ const unsigned char *rest;
++ size_t rest_size;
++ const char *word, *tag;
+ enum imap_client_input_state state = IMAP_CLIENT_INPUT_STATE_DONE_LF;
++ int ret;
+
+- /* skip over DONE[\r]\n */
+- if (i_memcasecmp(data, "DONE", I_MIN(size, 4)) != 0)
+- return IMAP_CLIENT_INPUT_STATE_BAD;
+- if (size <= 4)
++ /* read DONE */
++ word = imap_parser_read_word(parser);
++ if (word == NULL)
+ return IMAP_CLIENT_INPUT_STATE_UNKNOWN;
+- data += 4; size -= 4;
++ if (strcasecmp(word, "DONE") != 0)
++ return IMAP_CLIENT_INPUT_STATE_BAD;
+
+- if (data[0] == '\r') {
++ /* read [\r]\n after DONE */
++ rest = i_stream_get_data(input, &rest_size);
++ if (rest_size == 0)
++ return IMAP_CLIENT_INPUT_STATE_UNKNOWN;
++ if (rest[0] == '\r') {
+ state = IMAP_CLIENT_INPUT_STATE_DONE_CRLF;
+- data++; size--;
++ i_stream_skip(input, 1);
++ rest = i_stream_get_data(input, &rest_size);
++ if (rest_size == 0)
++ return IMAP_CLIENT_INPUT_STATE_UNKNOWN;
+ }
+- if (size == 0)
+- return IMAP_CLIENT_INPUT_STATE_UNKNOWN;
+- if (data[0] != '\n')
++ if (rest[0] != '\n')
+ return IMAP_CLIENT_INPUT_STATE_BAD;
+- data++; size--;
+- if (size == 0)
++ i_stream_skip(input, 1);
++
++ /* optionally followed by "<tag> IDLE[\r]\n" - checking this assumes
++ that the DONE and IDLE are sent in the same IP packet, otherwise
++ we'll unnecessarily recreate the imap process and immediately resume
++ IDLE there. if this becomes an issue we could add a small delay to
++ the imap process creation and wait for the IDLE command during it. */
++ rest = i_stream_get_data(input, &rest_size);
++ if (rest_size == 0)
+ return state;
+
+- tag_start = data;
+-
+- /* skip over tag */
+- while(size > 0 &&
+- data[0] != ' ' &&
+- data[0] != '\r' &&
+- data[0] != '\t' &&
+- data[0] != '\0') { data++; size--; }
+-
+- tag_end = data;
+-
+- if (size == 0)
++ ret = imap_parser_read_tag(parser, &tag);
++ if (ret <= 0)
+ return state;
+- if (data[0] != ' ')
+- return IMAP_CLIENT_INPUT_STATE_BAD;
+- data++; size--;
+-
+- /* skip over IDLE[\r]\n - checking this assumes that the DONE and IDLE
+- are sent in the same IP packet, otherwise we'll unnecessarily
+- recreate the imap process and immediately resume IDLE there. if this
+- becomes an issue we could add a small delay to the imap process
+- creation and wait for the IDLE command during it. */
+- if (size <= 4 || i_memcasecmp(data, "IDLE", 4) != 0)
++
++ /* read IDLE */
++ word = imap_parser_read_word(parser);
++ if (word == NULL || strcasecmp(word, "IDLE") != 0)
+ return state;
+- data += 4; size -= 4;
+
+- if (data[0] == '\r') {
+- data++; size--;
+- }
+- if (size == 1 && data[0] == '\n') {
+- *tag_r = t_strdup_until(tag_start, tag_end);
+- return IMAP_CLIENT_INPUT_STATE_DONEIDLE;
++ /* require [\r]\n and nothing else left */
++ rest = i_stream_get_data(input, &rest_size);
++ if (rest_size > 0 && rest[0] == '\r') {
++ i_stream_skip(input, 1);
++ rest = i_stream_get_data(input, &rest_size);
+ }
++ if (rest_size != 1 || rest[0] != '\n')
++ return state;
++
++ *tag_r = t_strdup(tag);
++ return IMAP_CLIENT_INPUT_STATE_DONEIDLE;
++}
++
++static enum imap_client_input_state
++imap_client_input_parse(const unsigned char *data, size_t size, const char **tag_r)
++{
++ struct istream *input = i_stream_create_from_data(data, size);
++ struct imap_parser *parser =
++ imap_parser_create(input, NULL, size, NULL);
++ enum imap_client_input_state state =
++ imap_client_input_parse_input(input, parser, tag_r);
++ imap_parser_unref(&parser);
++ i_stream_unref(&input);
+ return state;
+ }
+
+--
+2.47.3
+
--- /dev/null
+From 42864e4182c7bfe2931fdf888f3cb4ba77953dee Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 26 May 2025 09:45:56 +0300
+Subject: [PATCH] lda: Default mail_home=$HOME environment if not using userdb
+ lookup
+
+The previous code to do this was removed by
+e57d5b9002f910c095ee5b55821395fcf1da016a
+---
+ src/lda/main.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+Index: dovecot/src/lda/main.c
+===================================================================
+--- dovecot.orig/src/lda/main.c
++++ dovecot/src/lda/main.c
+@@ -478,12 +478,17 @@ int main(int argc, char *argv[])
+ "Couldn't lookup our username (uid=%s)",
+ dec2str(process_euid));
+ }
++ struct settings_root *set_root =
++ master_service_get_settings_root(master_service);
++ settings_root_override(set_root, "mail_home", home,
++ SETTINGS_OVERRIDE_TYPE_DEFAULT);
+ } else {
+ i_fatal_status(EX_USAGE,
+ "destination user parameter (-d user) not given");
+ }
+ struct master_service_settings_input set_input = {
+ .preserve_user = TRUE,
++ .preserve_home = TRUE,
+ };
+ struct master_service_settings_output set_output;
+ if (master_service_settings_read(master_service, &set_input,
--- /dev/null
+From bd11a6525eeb214541fa26450550f8b1bb4baec7 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 13 Apr 2026 12:33:51 +0200
+Subject: [PATCH 02/12] lib: Replace str_hash/strcase_hash with xxh64
+
+Replaces the old ASU-derived shift-and-XOR hash with xxh64_to_32(),
+which provides better distribution and avalanche properties.
+strcase_hash feeds each i_toupper()'d byte through the streaming API.
+---
+ src/lib/hash.c | 41 +++++++++++------------------------------
+ 1 file changed, 11 insertions(+), 30 deletions(-)
+
+diff --git a/src/lib/hash.c b/src/lib/hash.c
+index aceef29b23..8f4cd7d9b4 100644
+--- a/src/lib/hash.c
++++ b/src/lib/hash.c
+@@ -5,6 +5,7 @@
+ #include "lib.h"
+ #include "hash.h"
+ #include "primes.h"
++#include "xxh64.h"
+
+ #include <ctype.h>
+
+@@ -519,42 +520,22 @@ void hash_table_copy(struct hash_table *dest, struct hash_table *src)
+ hash_table_thaw(dest);
+ }
+
+-/* a char* hash function from ASU -- from glib */
+-unsigned int ATTR_NO_SANITIZE_INTEGER
+-str_hash(const char *p)
++unsigned int str_hash(const char *p)
+ {
+- const unsigned char *s = (const unsigned char *)p;
+- unsigned int g, h = 0;
+-
+- while (*s != '\0') {
+- h = (h << 4) + *s;
+- if ((g = h & 0xf0000000UL) != 0) {
+- h = h ^ (g >> 24);
+- h = h ^ g;
+- }
+- s++;
+- }
+-
+- return h;
++ return xxh64_to_32(xxh64_data(p, strlen(p), 0));
+ }
+
+-/* a char* hash function from ASU -- from glib */
+-unsigned int ATTR_NO_SANITIZE_INTEGER
+-strcase_hash(const char *p)
++unsigned int strcase_hash(const char *p)
+ {
+- const unsigned char *s = (const unsigned char *)p;
+- unsigned int g, h = 0;
++ struct xxh64_context ctx;
++ unsigned char c;
+
+- while (*s != '\0') {
+- h = (h << 4) + i_toupper(*s);
+- if ((g = h & 0xf0000000UL) != 0) {
+- h = h ^ (g >> 24);
+- h = h ^ g;
+- }
+- s++;
++ xxh64_init(&ctx, 0);
++ while (*p != '\0') {
++ c = (unsigned char)i_toupper(*p++);
++ xxh64_loop(&ctx, &c, 1);
+ }
+-
+- return h;
++ return xxh64_to_32(xxh64_result(&ctx));
+ }
+
+ unsigned int ATTR_NO_SANITIZE_INTEGER
+--
+2.47.3
+
--- /dev/null
+From 88ae095f0530417ccc619af0a9fb68b0ef7cf0c0 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Wed, 10 Jun 2026 11:54:00 +0000
+Subject: [PATCH 2/4] lib-compression: Iterate instead of recursing on
+ zero-output decompress chunks
+
+istream-lz4, istream-zlib and istream-bzlib each retried a read that
+produced no output by tail-calling their own read function. A crafted
+compressed stream can contain an unbounded number of chunks/steps that
+each decompress to zero bytes - e.g. an lz4 stream of single-byte chunks
+that each decode to nothing - so an attacker controlling the compressed
+data can drive recursion depth proportional to the chunk count. Tail-call
+optimization is not guaranteed, so this can exhaust the stack and crash
+the process reading the stream.
+
+Replace the recursive calls with continue inside the for(;;) loop
+introduced in the previous commit, keeping stack usage O(1). Add a
+regression test that feeds istream-lz4 a stream of 100000 empty chunks
+and reads it in a single call.
+---
+ src/lib-compression/istream-bzlib.c | 8 +++--
+ src/lib-compression/istream-lz4.c | 16 ++++++---
+ src/lib-compression/istream-zlib.c | 8 +++--
+ src/lib-compression/test-compression.c | 50 ++++++++++++++++++++++++++
+ 4 files changed, 74 insertions(+), 8 deletions(-)
+
+Index: trixie/src/lib-compression/istream-bzlib.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-bzlib.c
++++ trixie/src/lib-compression/istream-bzlib.c
+@@ -51,6 +51,10 @@ static ssize_t i_stream_bzlib_read(struc
+ size_t size, out_size;
+ int ret;
+
++ /* Loop instead of recursing when a decompress step yields no output:
++ a crafted stream can produce an unbounded number of zero-output
++ steps, and tail-call recursion (not guaranteed) would exhaust the
++ stack. */
+ for (;;) {
+ high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
+ if (zstream->eof_offset == high_offset) {
+@@ -130,8 +134,8 @@ static ssize_t i_stream_bzlib_read(struc
+ i_fatal("BZ2_bzDecompress() failed with %d", ret);
+ }
+ if (out_size == 0) {
+- /* read more input */
+- return i_stream_bzlib_read(stream);
++ /* no output yet; read more input without recursing */
++ continue;
+ }
+ return out_size;
+ }
+Index: trixie/src/lib-compression/istream-lz4.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-lz4.c
++++ trixie/src/lib-compression/istream-lz4.c
+@@ -149,6 +149,10 @@ static ssize_t i_stream_lz4_read(struct
+ zstream->header_read = TRUE;
+ }
+
++ /* Loop over chunks instead of recursing on zero-output chunks: a
++ crafted stream can contain an unbounded number of chunks that each
++ decompress to zero bytes, and tail-call recursion (which is not
++ guaranteed) would let that exhaust the stack. */
+ for (;;) {
+ if (zstream->chunk_left == 0) {
+ while ((ret = i_stream_lz4_read_chunk_header(zstream)) == 0) {
+@@ -183,8 +187,8 @@ static ssize_t i_stream_lz4_read(struct
+ if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream))
+ return -2;
+ if (i_stream_get_data_size(zstream->istream.parent) > 0) {
+- /* Parent stream was only partially consumed. Set the stream's
+- IO as pending to avoid hangs. */
++ /* Parent stream was only partially consumed. Set the
++ stream's IO as pending to avoid hangs. */
+ i_stream_set_input_pending(&zstream->istream.istream, TRUE);
+ }
+ /* allocate enough space for the old data and the new
+@@ -199,8 +203,12 @@ static ssize_t i_stream_lz4_read(struct
+ lz4_read_error(zstream, "corrupted lz4 chunk");
+ stream->istream.stream_errno = EINVAL;
+ return -1;
+- } else if (ret == 0)
+- return i_stream_lz4_read(stream);
++ } else if (ret == 0) {
++ /* chunk decompressed to zero bytes; read the next chunk
++ without recursing so the stack stays bounded. */
++ buffer_set_used_size(zstream->chunk_buf, 0);
++ continue;
++ }
+ i_assert(ret > 0);
+ stream->pos += ret;
+ i_assert(stream->pos <= stream->buffer_size);
+Index: trixie/src/lib-compression/istream-zlib.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-zlib.c
++++ trixie/src/lib-compression/istream-zlib.c
+@@ -168,6 +168,10 @@ static ssize_t i_stream_zlib_read(struct
+ size_t size, out_size;
+ int ret;
+
++ /* Loop instead of recursing when an inflate step yields no output:
++ a crafted stream can produce an unbounded number of zero-output
++ steps, and tail-call recursion (not guaranteed) would exhaust the
++ stack. */
+ for (;;) {
+ high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
+ if (zstream->eof_offset == high_offset) {
+@@ -314,8 +318,8 @@ static ssize_t i_stream_zlib_read(struct
+ i_fatal("inflate() failed with %d", ret);
+ }
+ if (out_size == 0) {
+- /* read more input */
+- return i_stream_zlib_read(stream);
++ /* no output yet; read more input without recursing */
++ continue;
+ }
+ return out_size;
+ }
+Index: trixie/src/lib-compression/test-compression.c
+===================================================================
+--- trixie.orig/src/lib-compression/test-compression.c
++++ trixie/src/lib-compression/test-compression.c
+@@ -1108,6 +1108,55 @@ static void test_lz4_chunk_size(void)
+ test_end();
+ }
+
++static void test_lz4_many_empty_chunks(void)
++{
++ const struct compression_handler *lz4;
++ struct istream *file_input, *input;
++
++ if (compression_lookup_handler("lz4", &lz4) <= 0)
++ return; /* not compiled in or unknown */
++
++ test_begin("lz4 many empty chunks");
++
++ /* A crafted lz4 stream can contain an unbounded number of chunks that
++ each decompress to zero bytes. i_stream_lz4_read() must iterate over
++ them rather than recurse once per chunk, or the stack is exhausted
++ (tail-call optimization is not guaranteed). */
++ buffer_t *buf = buffer_create_dynamic(default_pool, 1024*512);
++ struct iostream_lz4_header hdr;
++ memcpy(hdr.magic, IOSTREAM_LZ4_MAGIC, IOSTREAM_LZ4_MAGIC_LEN);
++ /* a valid (64k) max uncompressed chunk size, big-endian */
++ hdr.max_uncompressed_chunk_size[0] = 0x00;
++ hdr.max_uncompressed_chunk_size[1] = 0x01;
++ hdr.max_uncompressed_chunk_size[2] = 0x00;
++ hdr.max_uncompressed_chunk_size[3] = 0x00;
++ buffer_append(buf, &hdr, sizeof(hdr));
++
++ /* Each chunk: 4-byte big-endian compressed length (1), then a single
++ 0x00 byte, which LZ4_decompress_safe() decodes to zero bytes. */
++ static const unsigned char empty_chunk[] = {
++ 0x00, 0x00, 0x00, 0x01, 0x00
++ };
++ for (unsigned int i = 0; i < 100000; i++)
++ buffer_append(buf, empty_chunk, sizeof(empty_chunk));
++
++ file_input = test_istream_create_data(buf->data, buf->used);
++ file_input->blocking = TRUE;
++ input = lz4->create_istream(file_input);
++ i_stream_unref(&file_input);
++
++ /* All chunks are empty: clean EOF, no content, no error - and, with
++ the iterative read, no stack overflow. */
++ test_assert(i_stream_read(input) == -1);
++ test_assert(input->eof);
++ test_assert(input->stream_errno == 0);
++
++ i_stream_unref(&input);
++ buffer_free(&buf);
++
++ test_end();
++}
++
+ static void test_uncompress_file(const char *path)
+ {
+ const struct compression_handler *handler;
+@@ -1232,6 +1281,7 @@ int main(int argc, char *argv[])
+ test_gz_large_header,
+ test_lz4_small_header,
+ test_lz4_chunk_size,
++ test_lz4_many_empty_chunks,
+ test_compression_ext,
+ test_compression_deinit,
+ NULL
--- /dev/null
+From a6d847f3ad7076a3406a84774a90ab58a67f7908 Mon Sep 17 00:00:00 2001
+From: Marco Bettini <marco.bettini@open-xchange.com>
+Date: Mon, 20 Apr 2026 09:50:54 +0000
+Subject: [PATCH 2/4] lib-mail: o_stream_dot_sendv() - Fix boundary off by one
+
+---
+ src/lib-mail/ostream-dot.c | 2 +-
+ src/lib-mail/test-ostream-dot.c | 23 +++++++++++++++++++++++
+ 2 files changed, 24 insertions(+), 1 deletion(-)
+
+Index: trixie/src/lib-mail/ostream-dot.c
+===================================================================
+--- trixie.orig/src/lib-mail/ostream-dot.c
++++ trixie/src/lib-mail/ostream-dot.c
+@@ -105,7 +105,7 @@ o_stream_dot_sendv(struct ostream_privat
+
+ p = data;
+ pend = CONST_PTR_OFFSET(data, size);
+- for (; p < pend && (size_t)(p-data)+2 < max_bytes; p++) {
++ for (; p < pend && ((size_t)(p - data) + 2) <= max_bytes; p++) {
+ char add = 0;
+
+ size = pend - p;
+Index: trixie/src/lib-mail/test-ostream-dot.c
+===================================================================
+--- trixie.orig/src/lib-mail/test-ostream-dot.c
++++ trixie/src/lib-mail/test-ostream-dot.c
+@@ -92,11 +92,34 @@ static void test_ostream_dot_parent_almo
+ test_end();
+ }
+
++static void test_ostream_dot_parent_exact_fit(void)
++{
++ buffer_t *output_data;
++ struct ostream *test_output, *output;
++ ssize_t ret;
++
++ test_begin("dot ostream parent exact fit");
++ output_data = t_buffer_create(1024);
++ test_output = test_ostream_create_nonblocking(output_data, 2);
++ test_ostream_set_max_output_size(test_output, 2);
++
++ output = o_stream_create_dot(test_output, FALSE);
++ ret = o_stream_send(output, ".", 1);
++ test_assert(ret == 1);
++ test_assert_ucmp(output_data->used, ==, 2);
++ test_assert_memcmp(output_data->data, output_data->used, "..", 2);
++
++ o_stream_unref(&output);
++ o_stream_unref(&test_output);
++ test_end();
++}
++
+ int main(void)
+ {
+ static void (*const test_functions[])(void) = {
+ test_ostream_dot,
+ test_ostream_dot_parent_almost_full,
++ test_ostream_dot_parent_exact_fit,
+ NULL
+ };
+ return test_run(test_functions);
--- /dev/null
+From 5cc55540eef2096680e6074c27ea0524a2ecc6d9 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 4 May 2026 13:08:16 +0000
+Subject: [PATCH 2/5] lib: path-util - Add t_openat_safe() for jailed openat()
+ resolution
+
+Add a helper that opens a path relative to a directory fd while refusing
+any resolution that escapes that directory. Each component is opened with
+O_NOFOLLOW so symlinks are detected explicitly rather than transparently
+followed; symlinks are then resolved manually and only honoured when their
+(recursively resolved) target also stays beneath the base fd. Absolute
+symlink targets, leading '/', and '..' past the base are rejected with
+errno=ELOOP. Symlink chains are bounded by
+PATH_UTIL_OPENAT_SAFE_MAX_SYMLINKS hops.
+
+Anchoring resolution at a caller-held base_fd makes the lookup TOCTOU-safe
+even when intermediate path components are mutated on disk concurrently:
+the kernel resolves all openat() lookups relative to the original
+directory inode that base_fd refers to, so an attacker mutating path
+components on the filesystem cannot redirect resolution.
+
+This is the portable counterpart to Linux's openat2() with RESOLVE_BENEATH
+and is intended for callers that need to safely look up files under a
+user-writable directory (e.g. a Sieve script storage directory).
+---
+ src/lib/path-util.c | 170 +++++++++++++++++++++++++++++++++++++++
+ src/lib/path-util.h | 29 +++++++
+ src/lib/test-path-util.c | 124 ++++++++++++++++++++++++++++
+ 3 files changed, 323 insertions(+)
+
+Index: trixie/src/lib/path-util.c
+===================================================================
+--- trixie.orig/src/lib/path-util.c
++++ trixie/src/lib/path-util.c
+@@ -4,12 +4,17 @@
+ #include "str.h"
+ #include "path-util.h"
+
++#include <fcntl.h>
+ #include <unistd.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
+
+ #define PATH_UTIL_MAX_PATH 8*1024
+ #define PATH_UTIL_MAX_SYMLINKS 80
++/* Symlink-hop cap for t_openat_safe(). Lower than the realpath cap:
++ t_openat_safe() refuses absolute and escaping targets outright, so a
++ long chain inside the base dir is the only legitimate use. */
++#define PATH_UTIL_OPENAT_SAFE_MAX_SYMLINKS 16
+
+ static int t_getcwd_noalloc(char **dir_r, size_t *asize_r,
+ const char **error_r) ATTR_NULL(2)
+@@ -358,6 +363,171 @@ int t_readlink(const char *path, const c
+ return 0;
+ }
+
++/* Iteratively resolve `path` beneath `cur_fd` using openat() with
++ O_NOFOLLOW per component. Symlinks are resolved manually: their targets
++ are validated (non-absolute, no '..' past the base) and the loop
++ restarts with the target spliced into the remaining path. dir_depth
++ tracks how many directory levels we have descended below the original
++ base; '..' is refused when the depth would go negative.
++ */
++static int
++path_openat_safe_walk(int cur_fd, const char *path, int flags,
++ const char **error_r)
++{
++ bool cur_owned = FALSE;
++ unsigned int symlink_count = 0;
++ unsigned int dir_depth = 0;
++
++ /* Absolute paths cannot stay beneath the base directory. */
++ if (*path == '/') {
++ errno = ELOOP;
++ *error_r = "Path is absolute (escapes base directory)";
++ return -1;
++ }
++
++ for (;;) {
++ while (*path == '/')
++ path++;
++
++ if (*path == '\0') {
++ /* Empty path or trailing slash: open the current
++ directory itself with the requested flags. */
++ int fd = openat(cur_fd, ".",
++ flags | O_NOFOLLOW | O_CLOEXEC);
++ if (cur_owned)
++ i_close_fd(&cur_fd);
++ if (fd < 0) {
++ *error_r = t_strdup_printf(
++ "openat() failed: %m");
++ }
++ return fd;
++ }
++
++ const char *slash = strchr(path, '/');
++ size_t comp_len = (slash != NULL ?
++ (size_t)(slash - path) : strlen(path));
++ bool is_last = (slash == NULL);
++
++ if (comp_len == 1 && path[0] == '.') {
++ path += comp_len;
++ continue;
++ }
++
++ if (comp_len == 2 && path[0] == '.' && path[1] == '.') {
++ if (dir_depth == 0) {
++ if (cur_owned)
++ i_close_fd(&cur_fd);
++ errno = ELOOP;
++ *error_r = "Path escapes base directory via '..'";
++ return -1;
++ }
++ int parent_fd = openat(cur_fd, "..",
++ O_RDONLY | O_DIRECTORY |
++ O_NOFOLLOW | O_CLOEXEC);
++ if (cur_owned)
++ i_close_fd(&cur_fd);
++ if (parent_fd < 0) {
++ *error_r = t_strdup_printf(
++ "openat(..) failed: %m");
++ return -1;
++ }
++ cur_fd = parent_fd;
++ cur_owned = TRUE;
++ dir_depth--;
++ path += comp_len;
++ continue;
++ }
++
++ const char *comp = t_strdup_until(path, path + comp_len);
++ int next_fd;
++ if (is_last) {
++ next_fd = openat(cur_fd, comp,
++ flags | O_NOFOLLOW | O_CLOEXEC);
++ } else {
++ next_fd = openat(cur_fd, comp,
++ O_RDONLY | O_DIRECTORY |
++ O_NOFOLLOW | O_CLOEXEC);
++ }
++
++ if (next_fd < 0 && (errno == ELOOP || errno == EMLINK)) {
++ /* Component is a symlink. Resolve it ourselves and
++ restart processing with its target spliced into
++ `path`; that way the target is itself walked
++ component-by-component and the boundary check
++ continues to apply. (O_NOFOLLOW on a symlink reports
++ ELOOP on Linux/macOS but EMLINK on the BSDs, so both
++ must be treated as "this is a symlink".) */
++ if (++symlink_count >
++ PATH_UTIL_OPENAT_SAFE_MAX_SYMLINKS) {
++ if (cur_owned)
++ i_close_fd(&cur_fd);
++ errno = ELOOP;
++ *error_r = "Too many symlink dereferences";
++ return -1;
++ }
++
++ char buf[PATH_UTIL_MAX_PATH];
++ ssize_t llen = readlinkat(cur_fd, comp,
++ buf, sizeof(buf) - 1);
++ if (llen < 0) {
++ if (cur_owned)
++ i_close_fd(&cur_fd);
++ *error_r = t_strdup_printf(
++ "readlinkat(%s) failed: %m", comp);
++ return -1;
++ }
++ buf[llen] = '\0';
++
++ if (buf[0] == '/') {
++ if (cur_owned)
++ i_close_fd(&cur_fd);
++ errno = ELOOP;
++ *error_r = t_strdup_printf(
++ "Symlink '%s' has absolute target "
++ "'%s' (escapes base directory)",
++ comp, buf);
++ return -1;
++ }
++
++ path = is_last ? t_strdup(buf) :
++ t_strconcat(buf, "/",
++ slash + 1, NULL);
++ continue;
++ }
++
++ if (next_fd < 0) {
++ if (cur_owned)
++ i_close_fd(&cur_fd);
++ *error_r = t_strdup_printf(
++ "openat(%s) failed: %m", comp);
++ return -1;
++ }
++
++ if (is_last) {
++ if (cur_owned)
++ i_close_fd(&cur_fd);
++ return next_fd;
++ }
++
++ if (cur_owned)
++ i_close_fd(&cur_fd);
++ cur_fd = next_fd;
++ cur_owned = TRUE;
++ dir_depth++;
++ path = slash + 1;
++ }
++}
++
++int t_openat_safe(int base_fd, const char *path, int flags,
++ const char **error_r)
++{
++ i_assert(base_fd >= 0);
++ i_assert(path != NULL);
++ i_assert(error_r != NULL);
++
++ return path_openat_safe_walk(base_fd, path, flags, error_r);
++}
++
+ bool t_binary_abspath(const char **binpath, const char **error_r)
+ {
+ const char *path_env, *const *paths;
+Index: trixie/src/lib/path-util.h
+===================================================================
+--- trixie.orig/src/lib/path-util.h
++++ trixie/src/lib/path-util.h
+@@ -57,6 +57,35 @@ int t_get_working_dir(const char **dir_r
+ * -1 on failure. error_r is set on failure and cannot be NULL. */
+ int t_readlink(const char *path, const char **dest_r, const char **error_r);
+
++/* Open `path` relative to `base_fd`, refusing any resolution that escapes
++ the directory referenced by `base_fd`. Each path component is opened with
++ O_NOFOLLOW so symlinks are detected explicitly rather than transparently
++ followed; symlinks are followed only when their (recursively resolved)
++ target also stays beneath `base_fd`. Absolute symlink targets, leading
++ '/', and '..' past the base are rejected.
++
++ `base_fd` must reference a directory (e.g. obtained via
++ open(dir, O_DIRECTORY|O_RDONLY|O_CLOEXEC)) and remains owned by the
++ caller; this function does not close it.
++
++ `flags` is OR-ed into the openat() call for the final component (e.g.
++ O_RDONLY). O_NOFOLLOW and O_CLOEXEC are added implicitly.
++
++ Anchoring resolution at `base_fd` makes the lookup TOCTOU-safe even when
++ intermediate path components are mutated on disk concurrently: the kernel
++ resolves all component lookups relative to the original directory inode
++ `base_fd` references.
++
++ Returns the new fd (>= 0) on success. On failure returns -1 with errno
++ set and *error_r set to a descriptive message:
++ errno=ELOOP target escapes base, or symlink chain too deep
++ errno=ENOENT file not found
++ errno=EACCES permission denied
++ other errno as set by openat()/readlinkat()
++ */
++int t_openat_safe(int base_fd, const char *path, int flags,
++ const char **error_r);
++
+ /* Update binpath to be absolute:
+ * a) begins with '/' -> no change
+ * b) contains '/' -> assume relative to working directory
+Index: trixie/src/lib/test-path-util.c
+===================================================================
+--- trixie.orig/src/lib/test-path-util.c
++++ trixie/src/lib/test-path-util.c
+@@ -233,6 +233,129 @@ static void test_link_alloc2(void)
+ tmpdir = o_tmpdir;
+ }
+
++static void test_openat_safe(void)
++{
++ const char *error;
++ int base_fd, fd;
++
++ base_fd = open(tmpdir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
++ if (base_fd < 0)
++ i_fatal("open(%s) failed: %m", tmpdir);
++
++ /* Plain regular file inside base. */
++ const char *plain = t_strconcat(tmpdir, "/plain", NULL);
++ int wfd = creat(plain, 0600);
++ if (wfd < 0)
++ i_fatal("creat(%s) failed: %m", plain);
++ i_close_fd(&wfd);
++
++ fd = t_openat_safe(base_fd, "plain", O_RDONLY, &error);
++ test_assert(fd >= 0);
++ i_close_fd(&fd);
++
++ /* "./plain" and redundant slashes. */
++ fd = t_openat_safe(base_fd, "./plain", O_RDONLY, &error);
++ test_assert(fd >= 0);
++ i_close_fd(&fd);
++ fd = t_openat_safe(base_fd, ".//plain", O_RDONLY, &error);
++ test_assert(fd >= 0);
++ i_close_fd(&fd);
++
++ /* Symlink whose target is inside the base: allowed. */
++ const char *in_link = t_strconcat(tmpdir, "/in-link", NULL);
++ if (symlink("plain", in_link) < 0)
++ i_fatal("symlink(plain, %s) failed: %m", in_link);
++ fd = t_openat_safe(base_fd, "in-link", O_RDONLY, &error);
++ test_assert(fd >= 0);
++ i_close_fd(&fd);
++
++ /* Absolute symlink target: refused. */
++ const char *abs_link = t_strconcat(tmpdir, "/abs-link", NULL);
++ if (symlink("/etc/hostname", abs_link) < 0)
++ i_fatal("symlink failed: %m");
++ errno = 0;
++ fd = t_openat_safe(base_fd, "abs-link", O_RDONLY, &error);
++ test_assert(fd == -1);
++ test_assert(errno == ELOOP);
++ test_assert(error != NULL);
++
++ /* Relative symlink with '..' that escapes the base: refused. */
++ const char *escape_link = t_strconcat(tmpdir, "/escape-link", NULL);
++ if (symlink("../../etc/hostname", escape_link) < 0)
++ i_fatal("symlink failed: %m");
++ errno = 0;
++ fd = t_openat_safe(base_fd, "escape-link", O_RDONLY, &error);
++ test_assert(fd == -1);
++ test_assert(errno == ELOOP);
++ test_assert(error != NULL);
++
++ /* Direct '..' escape attempt: refused. */
++ errno = 0;
++ fd = t_openat_safe(base_fd, "../plain", O_RDONLY, &error);
++ test_assert(fd == -1);
++ test_assert(errno == ELOOP);
++
++ /* Absolute literal path: refused. */
++ errno = 0;
++ fd = t_openat_safe(base_fd, "/etc/hostname", O_RDONLY, &error);
++ test_assert(fd == -1);
++ test_assert(errno == ELOOP);
++
++ /* link2's target is an absolute path (tmpdir/link3): refused as an
++ absolute target before the loop is ever followed. */
++ errno = 0;
++ fd = t_openat_safe(base_fd, "link2", O_RDONLY, &error);
++ test_assert(fd == -1);
++ test_assert(errno == ELOOP);
++
++ /* Relative symlink loop staying inside the base: nothing rejects it
++ except the hop counter, so this exercises
++ PATH_UTIL_OPENAT_SAFE_MAX_SYMLINKS. */
++ const char *loopa = t_strconcat(tmpdir, "/loopa", NULL);
++ const char *loopb = t_strconcat(tmpdir, "/loopb", NULL);
++ if (symlink("loopb", loopa) < 0)
++ i_fatal("symlink(loopb, %s) failed: %m", loopa);
++ if (symlink("loopa", loopb) < 0)
++ i_fatal("symlink(loopa, %s) failed: %m", loopb);
++ errno = 0;
++ fd = t_openat_safe(base_fd, "loopa", O_RDONLY, &error);
++ test_assert(fd == -1);
++ test_assert(errno == ELOOP);
++ test_assert(error != NULL);
++
++ /* '..' that stays within the base: allowed. Create sub/under and
++ reach back to plain via sub/../plain. */
++ const char *subdir = t_strconcat(tmpdir, "/sub", NULL);
++ if (mkdir(subdir, 0700) < 0 && errno != EEXIST)
++ i_fatal("mkdir(%s) failed: %m", subdir);
++ fd = t_openat_safe(base_fd, "sub/../plain", O_RDONLY, &error);
++ test_assert(fd >= 0);
++ i_close_fd(&fd);
++
++ /* Breaking out of the base via subdirectory. */
++ errno = 0;
++ fd = t_openat_safe(base_fd, "sub/../../plain", O_RDONLY, &error);
++ test_assert(fd == -1);
++ test_assert(errno == ELOOP);
++
++ /* Nonexistent file inside base: ENOENT, not ELOOP. */
++ errno = 0;
++ fd = t_openat_safe(base_fd, "no-such-file", O_RDONLY, &error);
++ test_assert(fd == -1);
++ test_assert(errno == ENOENT);
++
++ i_close_fd(&base_fd);
++
++ i_unlink(plain);
++ i_unlink(in_link);
++ i_unlink(abs_link);
++ i_unlink(escape_link);
++ i_unlink(loopa);
++ i_unlink(loopb);
++ if (rmdir(subdir) < 0)
++ i_error("rmdir(%s) failed: %m", subdir);
++}
++
+ static void test_cleanup(void)
+ {
+ const char *error;
+@@ -272,6 +395,7 @@ void test_path_util(void)
+ test_abspath_vs_normpath();
+ test_link_alloc();
+ test_link_alloc2();
++ test_openat_safe();
+ test_cleanup();
+ alarm(0);
+ test_end();
--- /dev/null
+From 64e1f7af273809f484fb983d4a6edb1af1a85c99 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 5 May 2026 16:19:20 +0000
+Subject: [PATCH 2/7] lib-sieve: Drop binary-header resource usage tracking
+
+With the per-user sieve-rusage file now authoritative for personal
+storages, the .svbin header no longer needs to carry CPU usage. Admin
+storages (sieve_before/after, default location) cannot be re-uploaded
+by the user, so cross-run tracking is unnecessary there; the runtime
+sieve_max_cpu_time check still enforces the per-execution limit.
+
+Move the in-memory cumulative carry-over from sbin->header.resource_usage
+to a dedicated sbin->persisted_rusage substruct. The on-disk header
+struct now solely represents the wire format and is zeroed on every
+save; the in-memory carry-over no longer overloads it. Drop the now-dead
+"resource usage" section from sieve_binary_dumper_run() (the on-disk
+fields are always zero, so the section never produced output).
+---
+ src/lib-sieve/sieve-binary-dumper.c | 12 ---
+ src/lib-sieve/sieve-binary-file.c | 141 +++++----------------------
+ src/lib-sieve/sieve-binary-private.h | 6 ++
+ src/lib-sieve/sieve-binary.c | 15 ++-
+ 4 files changed, 37 insertions(+), 137 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary-dumper.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary-dumper.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary-dumper.c
+@@ -6,7 +6,6 @@
+ #include "ostream.h"
+ #include "array.h"
+ #include "buffer.h"
+-#include "time-util.h"
+
+ #include "sieve-common.h"
+ #include "sieve-extensions.h"
+@@ -117,17 +116,6 @@ bool sieve_binary_dumper_run(struct siev
+ "flags = 0x%08"PRIx32"\n",
+ header->version_major, header->version_minor,
+ header->flags);
+- if (header->resource_usage.update_time != 0) {
+- time_t update_time =
+- (time_t)header->resource_usage.update_time;
+- sieve_binary_dumpf(denv,
+- "resource usage:\n"
+- " update time = %s\n"
+- " cpu time = %"PRIu32" ms\n",
+- t_strflocaltime("%Y-%m-%d %H:%M:%S",
+- update_time),
+- header->resource_usage.cpu_time_msecs);
+- }
+
+ /* Dump list of binary blocks */
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary-file.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary-file.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary-file.c
+@@ -147,47 +147,15 @@ sieve_binary_file_read_header(struct sie
+ return 0;
+ }
+
+-static int
+-sieve_binary_file_write_header(struct sieve_binary *sbin, int fd,
+- struct sieve_binary_header *header,
+- enum sieve_error *error_code_r)
+-{
+- ssize_t wret;
+-
+- wret = pwrite(fd, header, sizeof(*header), 0);
+- if (wret < 0) {
+- e_error(sbin->event, "update: "
+- "failed to write to binary: %m");
+- *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
+- return -1;
+- } else if (wret != sizeof(*header)) {
+- e_error(sbin->event, "update: "
+- "header written partially %zd/%zu",
+- wret, sizeof(*header));
+- *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
+- return -1;
+- }
+- return 0;
+-}
+-
+-static void sieve_binary_file_update_header(struct sieve_binary *sbin)
++/* The on-disk binary header carries an unused resource_usage struct for
++ backward compatibility. Tracking lives in the per-user sieve-rusage file. */
++static void sieve_binary_file_zero_header_rusage(struct sieve_binary *sbin)
+ {
+ struct sieve_binary_header *header = &sbin->header;
+- struct sieve_resource_usage rusage;
+-
+- sieve_binary_get_resource_usage(sbin, &rusage);
+
+ i_zero(&header->resource_usage);
+- if (HAS_ALL_BITS(header->flags, SIEVE_BINARY_FLAG_RESOURCE_LIMIT) ||
+- sieve_resource_usage_is_high(sbin->svinst, &rusage)) {
+- header->resource_usage.update_time = ioloop_time;
+- header->resource_usage.cpu_time_msecs = rusage.cpu_time_msecs;
+- }
+-
+ sieve_resource_usage_init(&sbin->rusage);
+ sbin->rusage_updated = FALSE;
+-
+- (void)sieve_binary_check_resource_usage(sbin);
+ }
+
+ /*
+@@ -347,7 +315,7 @@ sieve_binary_save_to_stream(struct sieve
+ header->hdr_size = sizeof(*header);
+
+ header->flags &= ENUM_NEGATE(SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
+- sieve_binary_file_update_header(sbin);
++ sieve_binary_file_zero_header_rusage(sbin);
+
+ if (!_save_aligned(sbin, stream, header, sizeof(*header), NULL)) {
+ e_error(sbin->event, "save: failed to save header");
+@@ -864,6 +832,11 @@ _sieve_binary_open(struct sieve_binary *
+ return FALSE;
+ offset = sbin->header.hdr_size;
+
++ /* Resource usage tracking is no longer kept in the binary file;
++ discard any data from older versions. */
++ i_zero(&sbin->header.resource_usage);
++ sbin->header.flags &= ENUM_NEGATE(SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
++
+ /* Load block index */
+
+ for (i = 0; i < sbin->header.blocks && result; i++) {
+@@ -979,47 +952,27 @@ int sieve_binary_check_executable(struct
+ * Resource usage
+ */
+
+-static int
+-sieve_binary_file_do_update_resource_usage(
+- struct sieve_binary *sbin, int fd, enum sieve_error *error_code_r)
+-{
+- struct sieve_binary_header *header = &sbin->header;
+- struct file_lock *lock;
+- const char *error;
+- int ret;
+-
+- struct file_lock_settings lock_set = {
+- .lock_method = FILE_LOCK_METHOD_FCNTL,
+- };
+- ret = file_wait_lock(fd, sbin->path, F_WRLCK, &lock_set,
+- SIEVE_BINARY_FILE_LOCK_TIMEOUT, &lock, &error);
+- if (ret <= 0) {
+- e_error(sbin->event, "%s", error);
+- *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
+- return -1;
+- }
+-
+- ret = sieve_binary_file_read_header(sbin, fd, header, error_code_r);
+- if (ret == 0) {
+- sieve_binary_file_update_header(sbin);
+- ret = sieve_binary_file_write_header(sbin, fd, header,
+- error_code_r);
+- }
+-
+- file_lock_free(&lock);
+-
+- return ret;
+-}
+-
+-static int
+-sieve_binary_storage_update_resource_usage(struct sieve_binary *sbin,
+- struct sieve_storage *storage,
+- enum sieve_error *error_code_r)
++int sieve_binary_file_update_resource_usage(struct sieve_binary *sbin,
++ enum sieve_error *error_code_r)
+ {
++ struct sieve_storage *storage;
+ struct sieve_resource_usage delta;
+ struct sieve_resource_usage total;
+ uint32_t flags;
+
++ sieve_error_args_init(&error_code_r, NULL);
++
++ sieve_binary_file_close(&sbin->file);
++
++ if (sbin->script == NULL || sbin->script->storage == NULL ||
++ sbin->script->storage->rusage_path == NULL) {
++ /* No per-user file: tracking discarded. */
++ sieve_resource_usage_init(&sbin->rusage);
++ sbin->rusage_updated = FALSE;
++ return 0;
++ }
++ storage = sbin->script->storage;
++
+ delta = sbin->rusage;
+ sieve_binary_get_resource_usage(sbin, &total);
+ flags = sbin->header.flags & SIEVE_BINARY_FLAG_RESOURCE_LIMIT;
+@@ -1041,47 +994,3 @@ sieve_binary_storage_update_resource_usa
+ sbin->rusage_updated = FALSE;
+ return 0;
+ }
+-
+-int sieve_binary_file_update_resource_usage(struct sieve_binary *sbin,
+- enum sieve_error *error_code_r)
+-{
+- struct sieve_storage *storage = NULL;
+- int fd, ret = 0;
+-
+- sieve_error_args_init(&error_code_r, NULL);
+-
+- sieve_binary_file_close(&sbin->file);
+-
+- if (sbin->script != NULL && sbin->script->storage != NULL &&
+- sbin->script->storage->rusage_path != NULL)
+- storage = sbin->script->storage;
+-
+- if (storage != NULL) {
+- return sieve_binary_storage_update_resource_usage(
+- sbin, storage, error_code_r);
+- }
+-
+- if (sbin->path == NULL)
+- return 0;
+- if (sbin->header.version_major != SIEVE_BINARY_VERSION_MAJOR) {
+- return sieve_binary_save(sbin, sbin->path, TRUE, 0600,
+- error_code_r);
+- }
+-
+- fd = sieve_binary_fd_open(sbin, sbin->path, O_RDWR, error_code_r);
+- if (fd < 0) {
+- i_assert(*error_code_r != SIEVE_ERROR_NONE);
+- return -1;
+- }
+-
+- ret = sieve_binary_file_do_update_resource_usage(sbin, fd,
+- error_code_r);
+- i_assert(ret == 0 || *error_code_r != SIEVE_ERROR_NONE);
+-
+- if (close(fd) < 0) {
+- e_error(sbin->event, "update: "
+- "failed to close: close() failed: %m");
+- }
+-
+- return ret;
+-}
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary-private.h
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary-private.h
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary-private.h
+@@ -98,6 +98,12 @@ struct sieve_binary {
+ struct sieve_binary_header header;
+ struct sieve_resource_usage rusage;
+
++ /* Cumulative CPU usage loaded from per-user sieve-rusage file */
++ struct {
++ time_t update_time;
++ uint32_t cpu_time_msecs;
++ } persisted_rusage;
++
+ /* When the binary is loaded into memory or when it is being constructed
+ by the generator, extensions can be associated to the binary. The
+ extensions array is a sequential list of all linked extensions. The
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary.c
+@@ -218,8 +218,8 @@ void sieve_binary_apply_persisted_rusage
+ if (sieve_rusage_storage_load(storage, &rusage, &flags) <= 0)
+ return;
+
+- header->resource_usage.cpu_time_msecs = rusage.cpu_time_msecs;
+- header->resource_usage.update_time = ioloop_time;
++ sbin->persisted_rusage.cpu_time_msecs = rusage.cpu_time_msecs;
++ sbin->persisted_rusage.update_time = ioloop_time;
+ header->flags |= (flags & SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
+ sieve_resource_usage_init(&sbin->rusage);
+ sbin->rusage_updated = FALSE;
+@@ -228,15 +228,14 @@ void sieve_binary_apply_persisted_rusage
+ void sieve_binary_get_resource_usage(struct sieve_binary *sbin,
+ struct sieve_resource_usage *rusage_r)
+ {
+- struct sieve_binary_header *header = &sbin->header;
+- time_t update_time = header->resource_usage.update_time;
++ time_t update_time = sbin->persisted_rusage.update_time;
+ unsigned int timeout = sbin->svinst->set->resource_usage_timeout;
+
+ if (update_time != 0 && (ioloop_time - update_time) > (time_t)timeout)
+- i_zero(&header->resource_usage);
++ i_zero(&sbin->persisted_rusage);
+
+ sieve_resource_usage_init(rusage_r);
+- rusage_r->cpu_time_msecs = header->resource_usage.cpu_time_msecs;
++ rusage_r->cpu_time_msecs = sbin->persisted_rusage.cpu_time_msecs;
+ sieve_resource_usage_add(rusage_r, &sbin->rusage);
+ }
+
+@@ -278,9 +277,7 @@ bool sieve_binary_record_resource_usage(
+ void sieve_binary_set_resource_usage(struct sieve_binary *sbin,
+ const struct sieve_resource_usage *rusage)
+ {
+- struct sieve_binary_header *header = &sbin->header;
+-
+- i_zero(&header->resource_usage);
++ i_zero(&sbin->persisted_rusage);
+ sbin->rusage = *rusage;
+ sbin->rusage_updated = TRUE;
+
--- /dev/null
+From eec184745fabe1b2da0d5cc343f2a46710b70fc7 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 11 Jun 2026 19:33:23 +0000
+Subject: [PATCH 2/2] lib-sieve: actions - Create fileinto mailbox without the
+ autocreate flag
+
+Stop setting MAILBOX_FLAG_AUTO_CREATE/SUBSCRIBE in act_store. Instead, on
+mailbox_open() returning MAIL_ERROR_NOTFOUND, create the mailbox explicitly
+with sieve_act_store_create_mailbox() when autocreation is enabled (the
+:create side effect already creates it explicitly).
+
+This makes the creation go through the normal ACL CREATE gate, so a fileinto
+(with lda_mailbox_autocreate=yes) or fileinto :create can no longer create a
+mailbox in a shared or public namespace that the user lacks the CREATE right
+for.
+
+A permission failure to create the mailbox is classified by
+sieve_act_store_create_error_status(): for a user's personal script it is a
+permanent failure (implicit keep), while for a global (administrator-defined)
+script it is deferred (temporary failure), so the administrator notices the
+script is behaving in a way they did not intend.
+---
+ src/lib-sieve/sieve-actions.c | 56 +++++++++++++++++++++++++++--------
+ src/lib-sieve/sieve-actions.h | 11 ++++++-
+ 2 files changed, 53 insertions(+), 14 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-actions.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-actions.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-actions.c
+@@ -372,6 +372,31 @@ void sieve_act_store_get_storage_error(c
+ &trans->error_code));
+ }
+
++int sieve_act_store_create_error_status(
++ const struct sieve_action_exec_env *aenv, enum mail_error error_code)
++{
++ const struct sieve_execute_env *eenv = aenv->exec_env;
++
++ switch (error_code) {
++ case MAIL_ERROR_NONE:
++ return SIEVE_EXEC_OK;
++ case MAIL_ERROR_TEMP:
++ return SIEVE_EXEC_TEMP_FAILURE;
++ case MAIL_ERROR_PERM:
++ /* The mailbox cannot be created because of insufficient
++ permissions. This is a permanent condition. For a user's
++ personal script, fall through to implicit keep rather than
++ deferring the message. For a global (administrator-defined)
++ script, defer instead, so that the administrator notices the
++ script is behaving in a way they did not intend. */
++ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NOGLOBAL) != 0)
++ return SIEVE_EXEC_FAILURE;
++ return SIEVE_EXEC_TEMP_FAILURE;
++ default:
++ return SIEVE_EXEC_FAILURE;
++ }
++}
++
+ int sieve_act_store_create_mailbox(const struct sieve_action_exec_env *aenv,
+ struct act_store_transaction *trans)
+ {
+@@ -384,13 +409,13 @@ int sieve_act_store_create_mailbox(const
+ /* Create mailbox */
+ if (mailbox_create(box, NULL, FALSE) < 0) {
+ sieve_act_store_get_storage_error(aenv, trans);
+- if (trans->error_code == MAIL_ERROR_EXISTS) {
+- trans->error = NULL;
+- trans->error_code = MAIL_ERROR_NONE;
+- } else {
+- return (trans->error_code == MAIL_ERROR_TEMP ?
+- SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
++ if (trans->error_code != MAIL_ERROR_EXISTS) {
++ return sieve_act_store_create_error_status(
++ aenv, trans->error_code);
+ }
++ /* Lost a race with another session; the mailbox exists now. */
++ trans->error = NULL;
++ trans->error_code = MAIL_ERROR_NONE;
+ }
+
+ /* Subscribe to it if necessary */
+@@ -402,10 +427,9 @@ int sieve_act_store_create_mailbox(const
+
+ /* Try opening again */
+ if (mailbox_open(box) < 0) {
+- /* Failed definitively */
+ sieve_act_store_get_storage_error(aenv, trans);
+- return (trans->error_code == MAIL_ERROR_TEMP ?
+- SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
++ return sieve_act_store_create_error_status(
++ aenv, trans->error_code);
+ }
+ return SIEVE_EXEC_OK;
+ }
+@@ -434,10 +458,6 @@ act_store_mailbox_alloc(const struct sie
+ return FALSE;
+ }
+
+- if (eenv->scriptenv->mailbox_autocreate)
+- flags |= MAILBOX_FLAG_AUTO_CREATE;
+- if (eenv->scriptenv->mailbox_autosubscribe)
+- flags |= MAILBOX_FLAG_AUTO_SUBSCRIBE;
+ *box_r = box = mailbox_alloc_for_user(eenv->scriptenv->user, mailbox,
+ flags);
+ *storage = mailbox_get_storage(box);
+@@ -640,6 +660,16 @@ act_store_execute(const struct sieve_act
+ }
+ }
+
++ /* Create the mailbox if it does not exist yet and autocreation is
++ enabled (e.g. lda_mailbox_autocreate=yes). The :create side effect
++ creates it earlier; this handles a plain fileinto. */
++ if (trans->error_code == MAIL_ERROR_NOTFOUND &&
++ eenv->scriptenv->mailbox_autocreate) {
++ int cstatus = sieve_act_store_create_mailbox(aenv, trans);
++ if (cstatus != SIEVE_EXEC_OK)
++ return cstatus;
++ }
++
+ /* Exit early if transaction already failed */
+ switch (trans->error_code) {
+ case MAIL_ERROR_NONE:
+Index: trixie/pigeonhole/src/lib-sieve/sieve-actions.h
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-actions.h
++++ trixie/pigeonhole/src/lib-sieve/sieve-actions.h
+@@ -246,8 +246,17 @@ void sieve_act_store_add_flags(const str
+ void sieve_act_store_get_storage_error(const struct sieve_action_exec_env *aenv,
+ struct act_store_transaction *trans);
+
++/* Map a failure to create the target mailbox to an execution status. A
++ permanent failure (e.g. the user lacks permission to create the mailbox)
++ makes a user's personal script fall through to implicit keep, but is
++ deferred for a global (administrator-defined) script, so that the
++ unintended behaviour is surfaced to the administrator. */
++int sieve_act_store_create_error_status(
++ const struct sieve_action_exec_env *aenv, enum mail_error error_code);
++
+ /* Create the target mailbox of the store transaction (and subscribe to it
+- when configured). */
++ when configured). Returns SIEVE_EXEC_OK on success, or the status from
++ sieve_act_store_create_error_status() on failure. */
+ int sieve_act_store_create_mailbox(const struct sieve_action_exec_env *aenv,
+ struct act_store_transaction *trans);
+
--- /dev/null
+From 915cb2edc90da04d8a7a122fef7932cd55e3fff2 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 4 May 2026 13:10:13 +0000
+Subject: [PATCH 2/2] lib-sieve: storage: file - Validate script stat path
+ through dir_fd
+
+Extend the symlink-escape protection added in the previous commit to the
+stat performed by sieve_file_script_open(): an "include :personal" lookup
+or any other indirect path that triggers sieve_file_script_stat() also
+needs to refuse a symlink whose target leaves the personal storage
+directory, otherwise the existence check succeeds and the file is opened
+later via the safe path with an unhelpful "permission denied".
+
+Add sieve_file_script_stat_safe(), which uses fstatat(AT_SYMLINK_NOFOLLOW)
+to obtain the entry's own stat (lnk_st) and then, only if the entry is a
+symlink, opens it through sieve_file_storage_open_safe() to validate the
+target stays inside dir_fd and to fetch the resolved target's stat (st)
+via fstat(). Non-symlink entries skip the open entirely.
+
+Use the new helper in sieve_file_script_open() whenever fstorage->dir_fd
+is available, falling back to the unsafe lstat+stat variant for
+non-personal or single-file storages.
+---
+ .../storage/file/sieve-file-script.c | 78 ++++++++++++++++++-
+ 1 file changed, 75 insertions(+), 3 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
++++ trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
+@@ -285,6 +285,57 @@ sieve_file_script_stat(const char *path,
+ return 0;
+ }
+
++/* TOCTOU-safe variant of sieve_file_script_stat() for directory storages.
++ Resolves the script entry beneath fstorage->dir_fd, refusing symlinks
++ whose target escapes the storage directory. *st gets the (resolved)
++ target's stat; *lnk_st gets the directory entry's own stat (i.e.
++ AT_SYMLINK_NOFOLLOW), matching the semantics of the unsafe variant. */
++static int
++sieve_file_script_stat_safe(struct sieve_file_storage *fstorage,
++ const char *filename, struct stat *st,
++ struct stat *lnk_st, const char **error_r)
++{
++ int fd, saved_errno;
++
++ i_assert(fstorage->dir_fd >= 0);
++
++ if (fstatat(fstorage->dir_fd, filename, lnk_st,
++ AT_SYMLINK_NOFOLLOW) < 0) {
++ *error_r = t_strdup_printf(
++ "fstatat(%s/%s) failed: %m",
++ fstorage->path, filename);
++ return -1;
++ }
++
++ if (!S_ISLNK(lnk_st->st_mode)) {
++ *st = *lnk_st;
++ return 0;
++ }
++
++ /* Symlink: open it via the safe walker (which refuses any target
++ that leaves fstorage->dir_fd) and read st from the resulting fd.
++ The fd is only used to fetch the resolved stat and is closed
++ immediately. Open with O_NONBLOCK so that a target which resolves
++ to a FIFO or other special file inside the storage directory only
++ yields its stat instead of blocking the delivery process. */
++ if (sieve_file_storage_open_safe(fstorage, filename,
++ O_RDONLY | O_NONBLOCK,
++ &fd, error_r) < 0)
++ return -1;
++
++ if (fstat(fd, st) < 0) {
++ saved_errno = errno;
++ *error_r = t_strdup_printf(
++ "fstat() failed for '%s/%s': %m",
++ fstorage->path, filename);
++ i_close_fd(&fd);
++ errno = saved_errno;
++ return -1;
++ }
++ i_close_fd(&fd);
++ return 0;
++}
++
+ static const char *
+ path_split_filename(const char *path, const char **dir_path_r)
+ {
+@@ -353,7 +404,25 @@ static int sieve_file_script_open(struct
+ dir_path = path;
+
+ path = sieve_file_storage_path_extend(fstorage, filename);
+- ret = sieve_file_script_stat(path, &st, &lnk_st);
++ if (fstorage->dir_fd >= 0) {
++ const char *serror;
++
++ ret = sieve_file_script_stat_safe(
++ fstorage, filename, &st,
++ &lnk_st, &serror);
++ if (ret < 0 && errno == ELOOP) {
++ sieve_script_set_critical(
++ script,
++ "Failed to open sieve script: %s",
++ serror);
++ script->storage->error_code =
++ SIEVE_ERROR_NO_PERMISSION;
++ success = FALSE;
++ }
++ } else {
++ ret = sieve_file_script_stat(
++ path, &st, &lnk_st);
++ }
+ }
+
+ } else {
+@@ -451,8 +520,11 @@ sieve_file_script_get_stream(struct siev
+
+ /* For directory-based storage, open the script via the storage
+ directory fd so that path resolution refuses to follow symlinks
+- whose (recursive) target leaves the storage directory.
+- Single-file storages have no dir_fd, so fall back to plain open(). */
++ whose (recursive) target leaves the storage directory. The fd-based
++ walk also makes this TOCTOU-safe: even if intermediate path
++ components are mutated between the stat in sieve_file_script_open()
++ and this call, the resolution stays within the original directory.
++ Single-file storages have no dir_fd, so fall back to a plain open(). */
+ if (fstorage->dir_fd >= 0 && fscript->filename != NULL &&
+ *fscript->filename != '\0') {
+ if (sieve_file_storage_open_safe(fstorage, fscript->filename,
--- /dev/null
+From fe17ee9e58d2eb7dab38976518380279112c0bca Mon Sep 17 00:00:00 2001
+From: Stephan Bosch <stephan.bosch@open-xchange.com>
+Date: Wed, 29 Oct 2025 17:49:10 +0100
+Subject: [PATCH 2/3] lib-sieve: util: test-edit-mail - Adjust to changes in
+ dovecot core lib-test
+
+---
+ src/lib-sieve/util/test-edit-mail.c | 20 ++++++++++----------
+ 1 file changed, 10 insertions(+), 10 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/util/test-edit-mail.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/util/test-edit-mail.c
++++ trixie/pigeonhole/src/lib-sieve/util/test-edit-mail.c
+@@ -99,7 +99,7 @@ static void test_deinit_mail_user()
+ i_error("unlink_directory(%s) failed: %s", mail_home, error);
+ }
+
+-static void test_init(void)
++static void test_edit_mail_init(void)
+ {
+ test_pool = pool_alloconly_create(MEMPOOL_GROWING"test pool", 128);
+
+@@ -107,7 +107,7 @@ static void test_init(void)
+ test_raw_mail_user = mail_raw_user_create(test_mail_user);
+ }
+
+-static void test_deinit(void)
++static void test_edit_mail_deinit(void)
+ {
+ mail_user_unref(&test_raw_mail_user);
+ test_deinit_mail_user();
+@@ -186,7 +186,7 @@ static void test_edit_mail_concatenated(
+ const char *value;
+
+ test_begin("edit-mail - concatenated");
+- test_init();
++ test_edit_mail_init();
+
+ /* Compose the message */
+
+@@ -434,7 +434,7 @@ static void test_edit_mail_concatenated(
+ edit_mail_unwrap(&edmail);
+ mail_raw_close(&rawmail);
+ i_stream_unref(&input_msg);
+- test_deinit();
++ test_edit_mail_deinit();
+ test_end();
+ }
+
+@@ -696,7 +696,7 @@ static void test_edit_mail_big_header(vo
+ const char *value;
+
+ test_begin("edit-mail - big header");
+- test_init();
++ test_edit_mail_init();
+
+ /* compose the message */
+
+@@ -737,7 +737,7 @@ static void test_edit_mail_big_header(vo
+ edit_mail_unwrap(&edmail);
+ mail_raw_close(&rawmail);
+ i_stream_unref(&input_msg);
+- test_deinit();
++ test_edit_mail_deinit();
+ test_end();
+ }
+
+@@ -757,7 +757,7 @@ static void test_edit_mail_small_buffer(
+ unsigned int i;
+
+ test_begin("edit-mail - small buffer");
+- test_init();
++ test_edit_mail_init();
+
+ /* compose the message */
+
+@@ -806,7 +806,7 @@ static void test_edit_mail_small_buffer(
+ edit_mail_unwrap(&edmail);
+ mail_raw_close(&rawmail);
+ i_stream_unref(&input_msg);
+- test_deinit();
++ test_edit_mail_deinit();
+ test_end();
+ }
+
+@@ -820,7 +820,7 @@ static void test_edit_mail_empty(void)
+ const char *value;
+
+ test_begin("edit-mail - empty message");
+- test_init();
++ test_edit_mail_init();
+
+ /* Compose the message */
+
+@@ -862,7 +862,7 @@ static void test_edit_mail_empty(void)
+ edit_mail_unwrap(&edmail);
+ mail_raw_close(&rawmail);
+ i_stream_unref(&input_msg);
+- test_deinit();
++ test_edit_mail_deinit();
+ test_end();
+ }
+
--- /dev/null
+From 9582563d2c261cda1cf7c59fb262292d1438cd73 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Fri, 29 May 2026 11:21:26 +0000
+Subject: [PATCH 2/6] lib-sql: Extract sql_query_callback_delayed() helper
+
+---
+ src/lib-sql/sql-api.c | 23 ++++++++++++++++-------
+ 1 file changed, 16 insertions(+), 7 deletions(-)
+
+diff --git a/src/lib-sql/sql-api.c b/src/lib-sql/sql-api.c
+index 4db6913229..5ee13f9b32 100644
+--- a/src/lib-sql/sql-api.c
++++ b/src/lib-sql/sql-api.c
+@@ -109,6 +109,20 @@ static struct sql_result *sql_result_new_error(const char *error)
+ return &result->result;
+ }
+
++static void
++sql_query_callback_delayed(struct sql_db *db, struct sql_result *result,
++ sql_query_callback_t *callback, void *context)
++{
++ struct sql_query_result_delayed *cb =
++ i_new(struct sql_query_result_delayed, 1);
++ cb->db = db;
++ cb->result = result;
++ cb->callback = callback;
++ cb->context = context;
++ cb->to = timeout_add_short(0, sql_query_delayed_callback, cb);
++ DLLIST_PREPEND(&db->query_delayed_list, cb);
++}
++
+ void sql_drivers_init_without_drivers(void)
+ {
+ i_array_init(&sql_drivers, 8);
+@@ -347,13 +361,8 @@ void sql_query(struct sql_db *db, const char *query,
+ return;
+ }
+
+- struct sql_query_result_delayed *cb = i_new(struct sql_query_result_delayed, 1);
+- cb->db = db;
+- cb->result = sql_query_s(db, query);
+- cb->callback = callback;
+- cb->context = context;
+- cb->to = timeout_add_short(0, sql_query_delayed_callback, cb);
+- DLLIST_PREPEND(&db->query_delayed_list, cb);
++ sql_query_callback_delayed(db, sql_query_s(db, query),
++ callback, context);
+ }
+
+ struct sql_result *sql_query_s(struct sql_db *db, const char *query)
+--
+2.39.5
+
--- /dev/null
+From e6c952b3ab417f97e9bbc3534b3e40131e808b1e Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 11 Jun 2026 17:20:40 +0000
+Subject: [PATCH 2/2] lib-storage: Remove MAILBOX_FLAG_AUTO_CREATE and
+ MAILBOX_FLAG_AUTO_SUBSCRIBE
+
+These flags were set only by mail delivery (lib-lda) and Sieve fileinto
+(Pigeonhole), both of which now create the target mailbox explicitly with
+mailbox_create(). Nothing sets the flags anymore, so remove them and drop
+the flag checks from mailbox_is_autocreated() and mailbox_is_autosubscribed().
+
+As a result mailbox_is_autocreated() reflects only mailboxes that virtually
+already exist (the user's INBOX and admin-configured "auto" mailboxes), so
+the ACL plugin again enforces the CREATE right for delivery-autocreated
+mailboxes in shared and public namespaces.
+---
+ src/lib-storage-lua/mail-storage-lua.c | 2 --
+ src/lib-storage/mail-storage.c | 4 ----
+ src/lib-storage/mail-storage.h | 4 ----
+ 3 files changed, 10 deletions(-)
+
+Index: trixie/src/lib-storage/mail-storage.c
+===================================================================
+--- trixie.orig/src/lib-storage/mail-storage.c
++++ trixie/src/lib-storage/mail-storage.c
+@@ -1375,16 +1375,12 @@ bool mailbox_is_autocreated(struct mailb
+ {
+ if (box->inbox_user)
+ return TRUE;
+- if ((box->flags & MAILBOX_FLAG_AUTO_CREATE) != 0)
+- return TRUE;
+ return box->set != NULL &&
+ strcmp(box->set->autocreate, MAILBOX_SET_AUTO_NO) != 0;
+ }
+
+ bool mailbox_is_autosubscribed(struct mailbox *box)
+ {
+- if ((box->flags & MAILBOX_FLAG_AUTO_SUBSCRIBE) != 0)
+- return TRUE;
+ return box->set != NULL &&
+ strcmp(box->set->autocreate, MAILBOX_SET_AUTO_SUBSCRIBE) == 0;
+ }
+Index: trixie/src/lib-storage/mail-storage.h
+===================================================================
+--- trixie.orig/src/lib-storage/mail-storage.h
++++ trixie/src/lib-storage/mail-storage.h
+@@ -74,10 +74,6 @@ enum mailbox_flags {
+ quota updates (possibly resulting in broken quota). and This is
+ useful for example when deleting entire user accounts. */
+ MAILBOX_FLAG_DELETE_UNSAFE = 0x400,
+- /* Mailbox is created implicitly if it does not exist. */
+- MAILBOX_FLAG_AUTO_CREATE = 0x1000,
+- /* Mailbox is subscribed to implicitly when it is created automatically */
+- MAILBOX_FLAG_AUTO_SUBSCRIBE = 0x2000,
+ /* Run fsck for mailbox index before doing anything else. This may be
+ useful in fixing index corruption errors that aren't otherwise
+ detected and that are causing the full mailbox opening to fail. */
+Index: trixie/src/lib-storage/mail-storage-lua.c
+===================================================================
+--- trixie.orig/src/lib-storage/mail-storage-lua.c
++++ trixie/src/lib-storage/mail-storage-lua.c
+@@ -33,8 +33,6 @@ static struct dlua_table_values lua_stor
+ DLUA_TABLE_ENUM(MAILBOX_FLAG_NO_INDEX_FILES),
+ DLUA_TABLE_ENUM(MAILBOX_FLAG_KEEP_LOCKED),
+ DLUA_TABLE_ENUM(MAILBOX_FLAG_IGNORE_ACLS),
+- DLUA_TABLE_ENUM(MAILBOX_FLAG_AUTO_CREATE),
+- DLUA_TABLE_ENUM(MAILBOX_FLAG_AUTO_SUBSCRIBE),
+
+ DLUA_TABLE_ENUM(MAILBOX_SYNC_FLAG_FULL_READ),
+ DLUA_TABLE_ENUM(MAILBOX_SYNC_FLAG_FAST),
--- /dev/null
+From 01b4ddd1de4b5bfd499891f4b3592880b02c3854 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 23 Jun 2026 15:26:51 +0000
+Subject: [PATCH 2/2] lib-storage: Simplify mailbox_mkdir() using
+ mailbox_list_mkdir_root(NULL)
+
+mailbox_mkdir() looked up the root path via mailbox_list_get_root_forced()
+only to pass it straight to mailbox_list_mkdir_root(). Let mkdir_root()
+resolve the root itself by passing path=NULL, dropping the local variable.
+---
+ src/lib-storage/mail-storage.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+Index: trixie/src/lib-storage/mail-storage.c
+===================================================================
+--- trixie.orig/src/lib-storage/mail-storage.c
++++ trixie/src/lib-storage/mail-storage.c
+@@ -3441,12 +3441,10 @@ int mailbox_mkdir(struct mailbox *box, c
+ enum mailbox_list_path_type type)
+ {
+ const struct mailbox_permissions *perm = mailbox_get_permissions(box);
+- const char *root_dir;
+
+ if (!perm->gid_origin_is_mailbox_path) {
+ /* mailbox root directory doesn't exist, create it */
+- root_dir = mailbox_list_get_root_forced(box->list, type);
+- if (mailbox_list_mkdir_root(box->list, root_dir, type) < 0) {
++ if (mailbox_list_mkdir_root(box->list, NULL, type) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
--- /dev/null
+From fb14defe1804881a1df50c0b29679f9fdf8ff5b0 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 16 Apr 2026 16:26:47 +0000
+Subject: [PATCH 2/3] lib-storage: thread - Limit References: header msgid
+ count to prevent O(N^2) CPU usage
+
+Cap per-message References ingestion at MAIL_THREAD_REFERENCES_MAX (1000)
+in mail_thread_map_add_mail(). Without this limit a single crafted email
+with N unique Message-IDs triggers N(N-1)/2 ancestor traversals in
+thread_node_has_ancestor(), allowing unauthenticated DoS via mail delivery.
+---
+ src/lib-storage/index/index-thread-private.h | 4 ++++
+ src/lib-storage/index/index-thread.c | 3 ++-
+ 2 files changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/src/lib-storage/index/index-thread-private.h b/src/lib-storage/index/index-thread-private.h
+index ed2837b7bb..563c3a3d2d 100644
+--- a/src/lib-storage/index/index-thread-private.h
++++ b/src/lib-storage/index/index-thread-private.h
+@@ -22,6 +22,10 @@
+ #define MAIL_THREAD_NODE_REF_MSGID 0
+ #define MAIL_THREAD_NODE_REF_INREPLYTO 1
+ #define MAIL_THREAD_NODE_REF_REFERENCES1 2
++/* Maximum number of References: header message-IDs stored per message.
++ Prevents O(N²) CPU usage in thread_node_has_ancestor() when a single
++ crafted message contains an abnormally long reference chain. */
++#define MAIL_THREAD_REFERENCES_MAX 1000
+
+ struct mail_thread_node {
+ /* UID of the message, or 0 for dummy nodes */
+diff --git a/src/lib-storage/index/index-thread.c b/src/lib-storage/index/index-thread.c
+index e20a97072c..a58f9681f3 100644
+--- a/src/lib-storage/index/index-thread.c
++++ b/src/lib-storage/index/index-thread.c
+@@ -280,7 +280,8 @@ mail_thread_map_add_mail(struct mail_thread_context *ctx, struct mail *mail)
+ ref_index, msgid);
+ }
+ } T_END;
+- } while (msgid != NULL);
++ } while (msgid != NULL &&
++ ref_index < MAIL_THREAD_NODE_REF_REFERENCES1 + MAIL_THREAD_REFERENCES_MAX);
+ } else {
+ /* no References:, use In-Reply-To: */
+ if (thread_get_mail_header(mail, HDR_IN_REPLY_TO,
+--
+2.47.3
+
--- /dev/null
+From 54211965c41eb15290bae017b8acfd95d08e0a9e Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Sun, 3 May 2026 16:24:38 +0000
+Subject: [PATCH 2/2] login-common: Avoid array_front() panic on empty
+ forward_fields array
+
+sasl_server_auth_request_info_fill() and proxy_redirect_reauth()
+NUL-terminate the forward_fields array via array_append_zero() +
+array_pop_back() and then call array_front() to hand the C array to
+the auth client. array_front() asserts when the array is empty, so a
+created-but-empty forward_fields array crashes login.
+
+The empty-array case is no longer reachable from
+client_forward_decode_base64() after the previous commit, but guard
+defensively here as well: any future caller that creates the array
+without populating it should not be able to panic the process.
+---
+ src/login-common/client-common-auth.c | 2 +-
+ src/login-common/sasl-server.c | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/login-common/client-common-auth.c b/src/login-common/client-common-auth.c
+index 0b226497c6..36ed7211a7 100644
+--- a/src/login-common/client-common-auth.c
++++ b/src/login-common/client-common-auth.c
+@@ -490,7 +490,7 @@ proxy_redirect_reauth(struct client *client, const char *destuser,
+ t_array_init(&info.extra_fields, N_ELEMENTS(extra_fields));
+ array_append(&info.extra_fields, extra_fields,
+ N_ELEMENTS(extra_fields));
+- if (array_is_created(&client->forward_fields)) {
++ if (array_not_empty(&client->forward_fields)) {
+ array_append_zero(&client->forward_fields);
+ array_pop_back(&client->forward_fields);
+ info.forward_fields = array_front(&client->forward_fields);
+diff --git a/src/login-common/sasl-server.c b/src/login-common/sasl-server.c
+index f02262c00f..88d46e7c68 100644
+--- a/src/login-common/sasl-server.c
++++ b/src/login-common/sasl-server.c
+@@ -534,7 +534,7 @@ int sasl_server_auth_request_info_fill(struct client *client,
+ info_r->real_remote_port = client->real_remote_port;
+ if (client->client_id != NULL)
+ info_r->client_id = str_c(client->client_id);
+- if (array_is_created(&client->forward_fields)) {
++ if (array_not_empty(&client->forward_fields)) {
+ array_append_zero(&client->forward_fields);
+ array_pop_back(&client->forward_fields);
+ info_r->forward_fields = array_front(&client->forward_fields);
+--
+2.47.3
+
--- /dev/null
+From eee1ecfc512d348f8fb60633b6bd26b1536319da Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Mon, 25 May 2026 10:38:13 +0000
+Subject: [PATCH 2/2] managesieve-login: client_skip_line() - Discard data when
+ newline is not found
+
+Without this, data without a newline (e.g., from a lone CR protocol violation
+detected by the parser) stays in the buffer indefinitely. This prevents the
+server from ever detecting client disconnect, causing a deadlock where both
+sides wait for the other.
+
+The post-login server-side client_skip_line() in managesieve-client.c already
+handles this correctly by always calling i_stream_skip().
+---
+ src/managesieve-login/client.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+Index: trixie/pigeonhole/src/managesieve-login/client.c
+===================================================================
+--- trixie.orig/pigeonhole/src/managesieve-login/client.c
++++ trixie/pigeonhole/src/managesieve-login/client.c
+@@ -51,6 +51,7 @@ bool client_skip_line(struct managesieve
+ }
+ }
+
++ i_stream_skip(client->input, data_size);
+ return FALSE;
+ }
+
--- /dev/null
+From f0bb034096518187b2b97ab25cd62a6d5517a150 Mon Sep 17 00:00:00 2001
+From: Stephan Bosch <stephan.bosch@open-xchange.com>
+Date: Fri, 28 Nov 2025 04:12:10 +0100
+Subject: [PATCH 2/5] submission-login: client - Fix panic occurring at
+ mail_max_userip_connections limit transgression
+
+Panic was:
+Panic: epoll_ctl(del, 8) failed: Bad file descriptor
+
+Fixed by making sure the underlying smtp-server connection is always closed before the connection FD is closed.
+---
+ src/lib-smtp/smtp-server-connection.c | 5 +++++
+ src/lib-smtp/smtp-server.h | 1 +
+ src/submission-login/client.c | 16 ++++++++++++++++
+ 3 files changed, 22 insertions(+)
+
+Index: trixie/src/lib-smtp/smtp-server-connection.c
+===================================================================
+--- trixie.orig/src/lib-smtp/smtp-server-connection.c
++++ trixie/src/lib-smtp/smtp-server-connection.c
+@@ -1401,6 +1401,11 @@ void smtp_server_connection_close(struct
+ smtp_server_connection_unref(&conn);
+ }
+
++bool smtp_server_connection_is_closed(struct smtp_server_connection *conn)
++{
++ return conn->closed;
++}
++
+ void smtp_server_connection_terminate(struct smtp_server_connection **_conn,
+ const char *enh_code, const char *reason)
+ {
+Index: trixie/src/lib-smtp/smtp-server.h
+===================================================================
+--- trixie.orig/src/lib-smtp/smtp-server.h
++++ trixie/src/lib-smtp/smtp-server.h
+@@ -476,6 +476,7 @@ void smtp_server_connection_set_ssl_stre
+
+ void smtp_server_connection_close(struct smtp_server_connection **_conn,
+ const char *reason) ATTR_NULL(2);
++bool smtp_server_connection_is_closed(struct smtp_server_connection *conn);
+ void smtp_server_connection_terminate(struct smtp_server_connection **_conn,
+ const char *enh_code, const char *reason)
+ ATTR_NULL(3);
+Index: trixie/src/submission-login/client.c
+===================================================================
+--- trixie.orig/src/submission-login/client.c
++++ trixie/src/submission-login/client.c
+@@ -146,6 +146,21 @@ static int submission_client_create(stru
+ return 0;
+ }
+
++static void
++submission_client_disconnect(struct client *client, const char *reason)
++{
++ struct submission_client *subm_client =
++ container_of(client, struct submission_client, common);
++
++ /* If the smtp-server connection is already in its close cascade (i.e.
++ we're being called via its conn_disconnect callback), skip closing
++ it again. Doing so would null subm_client->conn and prevent the
++ conn_free callback from invoking client_destroy(). */
++ if (subm_client->conn != NULL &&
++ !smtp_server_connection_is_closed(subm_client->conn))
++ smtp_server_connection_close(&subm_client->conn, reason);
++}
++
+ static void submission_client_destroy(struct client *client)
+ {
+ struct submission_client *subm_client =
+@@ -320,6 +335,7 @@ static const struct smtp_server_callback
+ static struct client_vfuncs submission_client_vfuncs = {
+ .alloc = submission_client_alloc,
+ .create = submission_client_create,
++ .disconnect = submission_client_disconnect,
+ .destroy = submission_client_destroy,
+ .reload_config = submission_client_reload_config,
+ .notify_auth_ready = submission_client_notify_auth_ready,
--- /dev/null
+From fd7d4fddf1798b118b0c5481a0dabe03250829ca Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Fri, 29 May 2026 07:31:50 +0000
+Subject: [PATCH 3/4] auth: db-oauth2: Add oauth2_audience setting, deprecate
+ aud-as-scope fallback
+
+Add a dedicated oauth2_audience setting checked against the token's aud
+claim (RFC 7519 section 4.1.3) using the same tab-split AND-semantics as
+oauth2_scope. Emit a deprecation warning when the existing aud fallback
+in db_oauth2_token_in_scope() is triggered so operators know to migrate.
+---
+ src/auth/db-oauth2.c | 53 ++++++++++++++++++++++++++++++++++++++++++--
+ src/auth/db-oauth2.h | 2 ++
+ 2 files changed, 53 insertions(+), 2 deletions(-)
+
+Index: trixie/src/auth/db-oauth2.c
+===================================================================
+--- trixie.orig/src/auth/db-oauth2.c
++++ trixie/src/auth/db-oauth2.c
+@@ -17,16 +17,21 @@
+ #include "db-oauth2.h"
+ #include "dcrypt.h"
+ #include "dict.h"
++#include <time.h>
+
+ #undef DEF
+ #define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type("oauth2_"#name, name, struct auth_oauth2_settings)
+
++#define WARN_AUD_FALLBACK_PERIOD 600
++static time_t last_warned_aud_fallback = 0;
++
+ static const struct setting_define auth_oauth2_setting_defines[] = {
+ DEF(STR, tokeninfo_url),
+ DEF(STR, grant_url),
+ DEF(STR, introspection_url),
+ DEF(BOOLLIST, scope),
++ DEF(BOOLLIST, audience),
+ DEF(ENUM, introspection_mode),
+ DEF(STR_NOVARS, username_validation_format),
+ DEF(STR, username_attribute),
+@@ -50,6 +55,7 @@ static const struct auth_oauth2_settings
+ .grant_url = "",
+ .introspection_url = "",
+ .scope = ARRAY_INIT,
++ .audience = ARRAY_INIT,
+ .force_introspection = FALSE,
+ .introspection_mode = ":auth:get:post:local",
+ .username_validation_format = "%{user}",
+@@ -558,8 +564,19 @@ db_oauth2_token_in_scope(struct db_oauth
+
+ const char *value = auth_fields_find(req->fields, "scope");
+ bool has_scope = value != NULL;
+- if (!has_scope)
++ if (!has_scope && array_is_empty(&req->db->set->audience)) {
+ value = auth_fields_find(req->fields, "aud");
++ if (value != NULL) {
++ time_t t0 = time(NULL);
++ if (t0 - last_warned_aud_fallback > WARN_AUD_FALLBACK_PERIOD) {
++ e_warning(authdb_event(req->auth_request),
++ "Token has no 'scope' claim; falling back to "
++ "'aud' for scope check is deprecated - "
++ "use oauth2_audience instead");
++ last_warned_aud_fallback = t0;
++ }
++ }
++ }
+ e_debug(authdb_event(req->auth_request),
+ "Token scope(s): %s", value);
+
+@@ -585,6 +602,37 @@ db_oauth2_token_in_scope(struct db_oauth
+ return found;
+ }
+
++static bool
++db_oauth2_token_in_audience(struct db_oauth2_request *req,
++ enum passdb_result *result_r, const char **error_r)
++{
++ if (array_is_empty(&req->db->set->audience))
++ return TRUE;
++
++ const char *value = auth_fields_find(req->fields, "aud");
++ e_debug(authdb_event(req->auth_request),
++ "Token audience(s): %s", value != NULL ? value : "(none)");
++
++ bool found = FALSE;
++ if (value != NULL && *value != '\0') {
++ const char *const *entries = t_strsplit_tabescaped(value);
++ const char *wanted;
++ found = TRUE;
++ array_foreach_elem(&req->db->set->audience, wanted) {
++ if (!str_array_find(entries, wanted)) {
++ found = FALSE;
++ break;
++ }
++ }
++ }
++ if (!found) {
++ *error_r = t_strdup_printf("Token audience does not include '%s'",
++ t_array_const_string_join(&req->db->set->audience, " "));
++ *result_r = PASSDB_RESULT_USER_DISABLED;
++ }
++ return found;
++}
++
+ static void db_oauth2_process_fields(struct db_oauth2_request *req,
+ enum passdb_result *result_r,
+ const char **error_r)
+@@ -593,7 +641,8 @@ static void db_oauth2_process_fields(str
+
+ if (db_oauth2_user_is_enabled(req, result_r, error_r) &&
+ db_oauth2_validate_username(req, result_r, error_r) &&
+- db_oauth2_token_in_scope(req, result_r, error_r)) {
++ db_oauth2_token_in_scope(req, result_r, error_r) &&
++ db_oauth2_token_in_audience(req, result_r, error_r)) {
+ /* The user has now been successfully authenticated,
+ mark the request as such. This allows having no
+ passdb in config. */
+Index: trixie/src/auth/db-oauth2.h
+===================================================================
+--- trixie.orig/src/auth/db-oauth2.h
++++ trixie/src/auth/db-oauth2.h
+@@ -15,6 +15,8 @@ struct auth_oauth2_settings {
+ const char *introspection_url;
+ /* expected scope(s), optional */
+ ARRAY_TYPE(const_string) scope;
++ /* expected audience(s) (aud claim), optional */
++ ARRAY_TYPE(const_string) audience;
+ /* mode of introspection, one of auth, get, post, local
+ - auth: send token with header Authorization: Bearer token
+ - get: append token to url
--- /dev/null
+From 161256a377c194aa8e63c83053c35cdd1ab75eda Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 7 Jul 2026 14:33:40 +0300
+Subject: [PATCH 3/3] doveadm compress: Delay failing if used COMPRESS
+ algorithm is not supported locally
+
+This command is used in CI tests to verify the server rejects non-DEFLATE
+algorithm.
+---
+ src/doveadm/doveadm-compress.c | 9 ++++++++-
+ 1 file changed, 8 insertions(+), 1 deletion(-)
+
+Index: trixie/src/doveadm/doveadm-compress.c
+===================================================================
+--- trixie.orig/src/doveadm/doveadm-compress.c
++++ trixie/src/doveadm/doveadm-compress.c
+@@ -123,9 +123,14 @@ client_input_get_compress_algorithm(stru
+ if (!str_begins_icase(line, " COMPRESS ", &algorithm))
+ return FALSE;
+
++ /* Look up the local handler needed to (de)compress the stream once the
++ server accepts. Don't fail here if the mechanism is unknown or not
++ compiled in: the server may reject it, and this client must still send
++ the command to let the server do so. If the server unexpectedly
++ accepts a mechanism we can't handle, we fail then (see server_input()). */
+ if (compression_lookup_handler(t_str_lcase(algorithm),
+ &client->handler) <= 0)
+- i_fatal("Unsupported compression mechanism: %s", algorithm);
++ client->handler = NULL;
+ /* Remember the tag so we can tell whether the server accepted or
+ rejected this COMPRESS command. */
+ i_free(client->compress_tag);
+@@ -281,6 +286,8 @@ static void server_input(struct client *
+ struct istream *input;
+ struct ostream *output;
+
++ if (client->handler == NULL)
++ i_fatal("Server accepted unsupported compression mechanism");
+ e_info(client->event, "<Compression started>");
+ input = client->handler->create_istream(client->input);
+ output = client->handler->
--- /dev/null
+From e14d6ae2825adf24f58e397e3cf88d8c9f41e8a2 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 17 Apr 2026 16:55:34 +0200
+Subject: [PATCH 03/14] imap: Stream ENVELOPE to client without ostream memory
+ duplication
+
+Same fix as for BODYSTRUCTURE: avoid copying the envelope string into
+the ostream ring buffer by using o_stream_set_max_buffer_size(0) +
+o_stream_send_istream() with a continuation handler.
+
+Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
+---
+ src/imap/imap-fetch.c | 38 ++++++++++++++++++++++++++++++++++----
+ 1 file changed, 34 insertions(+), 4 deletions(-)
+
+diff --git a/src/imap/imap-fetch.c b/src/imap/imap-fetch.c
+index b4a8a465d7..5a6629916b 100644
+--- a/src/imap/imap-fetch.c
++++ b/src/imap/imap-fetch.c
+@@ -793,6 +793,34 @@ static bool fetch_bodystructure_init(struct imap_fetch_init_context *ctx)
+ return TRUE;
+ }
+
++static int fetch_envelope_stream_continue(struct imap_fetch_context *ctx)
++{
++ struct imap_fetch_state *state = &ctx->state;
++ enum ostream_send_istream_result res;
++
++ o_stream_set_max_buffer_size(ctx->client->output, 0);
++ res = o_stream_send_istream(ctx->client->output, state->cur_input);
++ o_stream_set_max_buffer_size(ctx->client->output, SIZE_MAX);
++
++ switch (res) {
++ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
++ i_stream_unref(&state->cur_input);
++ state->cont_handler = NULL;
++ if (o_stream_send(ctx->client->output, ")", 1) < 0)
++ return -1;
++ return 1;
++ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
++ i_unreached();
++ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
++ return 0;
++ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
++ i_unreached();
++ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
++ return -1;
++ }
++ i_unreached();
++}
++
+ static int fetch_envelope(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+ {
+@@ -808,11 +836,13 @@ static int fetch_envelope(struct imap_fetch_context *ctx, struct mail *mail,
+ return -1;
+ }
+
+- if (o_stream_send(ctx->client->output, "ENVELOPE (", 10) < 0 ||
+- o_stream_send_str(ctx->client->output, envelope) < 0 ||
+- o_stream_send(ctx->client->output, ")", 1) < 0)
++ if (o_stream_send(ctx->client->output, "ENVELOPE (", 10) < 0)
+ return -1;
+- return 1;
++
++ ctx->state.cur_input =
++ i_stream_create_from_data(envelope, strlen(envelope));
++ ctx->state.cont_handler = fetch_envelope_stream_continue;
++ return fetch_envelope_stream_continue(ctx);
+ }
+
+ static bool fetch_envelope_init(struct imap_fetch_init_context *ctx)
+--
+2.47.3
+
--- /dev/null
+From 4778fe2357a1fcaa45d566f5095e80d8be8f4e4b Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 13 Apr 2026 17:20:46 +0200
+Subject: [PATCH 03/12] lib: Add str_stable_hash()
+
+For now this is the same as str_hash(), but this is changed in a following
+commit.
+---
+ src/lib/hash.c | 5 +++++
+ src/lib/hash.h | 1 +
+ 2 files changed, 6 insertions(+)
+
+diff --git a/src/lib/hash.c b/src/lib/hash.c
+index 8f4cd7d9b4..61fe86ed8e 100644
+--- a/src/lib/hash.c
++++ b/src/lib/hash.c
+@@ -525,6 +525,11 @@ unsigned int str_hash(const char *p)
+ return xxh64_to_32(xxh64_data(p, strlen(p), 0));
+ }
+
++unsigned int str_stable_hash(const char *p)
++{
++ return xxh64_to_32(xxh64_data(p, strlen(p), 0));
++}
++
+ unsigned int strcase_hash(const char *p)
+ {
+ struct xxh64_context ctx;
+diff --git a/src/lib/hash.h b/src/lib/hash.h
+index 5716a77a7d..84d9c52aef 100644
+--- a/src/lib/hash.h
++++ b/src/lib/hash.h
+@@ -171,6 +171,7 @@ void hash_table_copy(struct hash_table *dest, struct hash_table *src);
+ /* hash function for strings */
+ unsigned int str_hash(const char *p) ATTR_PURE;
+ unsigned int strcase_hash(const char *p) ATTR_PURE;
++unsigned int str_stable_hash(const char *p) ATTR_PURE;
+
+ /* fast hash function which uppercases a-z. Does not work well
+ with input that consists from non number/letter input, as
+--
+2.47.3
+
--- /dev/null
+From ba9ae23ee80dece8ebc3bb6b28c42b35002afb96 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 3 Jun 2026 18:10:36 +0300
+Subject: [PATCH 3/5] lib: Add t_openat_safe_dir()
+
+---
+ src/lib/path-util.c | 19 +++++++++++++++
+ src/lib/path-util.h | 7 ++++++
+ src/lib/test-path-util.c | 52 ++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 78 insertions(+)
+
+Index: trixie/src/lib/path-util.c
+===================================================================
+--- trixie.orig/src/lib/path-util.c
++++ trixie/src/lib/path-util.c
+@@ -528,6 +528,25 @@ int t_openat_safe(int base_fd, const cha
+ return path_openat_safe_walk(base_fd, path, flags, error_r);
+ }
+
++int t_openat_safe_dir(const char *base_dir, const char *path, int flags,
++ const char **error_r)
++{
++ i_assert(base_dir != NULL);
++ i_assert(path != NULL);
++ i_assert(error_r != NULL);
++
++ int dir_fd = open(base_dir,
++ O_DIRECTORY | O_RDONLY | O_CLOEXEC |
++ (flags & O_NOFOLLOW));
++ if (dir_fd < 0) {
++ *error_r = t_strdup_printf("open(%s) failed: %m", base_dir);
++ return -1;
++ }
++ int fd = t_openat_safe(dir_fd, path, flags, error_r);
++ i_close_fd(&dir_fd);
++ return fd;
++}
++
+ bool t_binary_abspath(const char **binpath, const char **error_r)
+ {
+ const char *path_env, *const *paths;
+Index: trixie/src/lib/path-util.h
+===================================================================
+--- trixie.orig/src/lib/path-util.h
++++ trixie/src/lib/path-util.h
+@@ -86,6 +86,13 @@ int t_readlink(const char *path, const c
+ int t_openat_safe(int base_fd, const char *path, int flags,
+ const char **error_r);
+
++/* Convenience wrapper: opens base_dir as a directory fd, then delegates to
++ t_openat_safe(). If O_NOFOLLOW is present in flags it is also applied to
++ the open() of base_dir itself, refusing a symlink as its final component.
++ Returns the new fd on success, -1 on failure. */
++int t_openat_safe_dir(const char *base_dir, const char *path, int flags,
++ const char **error_r);
++
+ /* Update binpath to be absolute:
+ * a) begins with '/' -> no change
+ * b) contains '/' -> assume relative to working directory
+Index: trixie/src/lib/test-path-util.c
+===================================================================
+--- trixie.orig/src/lib/test-path-util.c
++++ trixie/src/lib/test-path-util.c
+@@ -356,6 +356,57 @@ static void test_openat_safe(void)
+ i_error("rmdir(%s) failed: %m", subdir);
+ }
+
++static void test_openat_safe_dir(void)
++{
++ const char *error;
++ int fd;
++
++ /* Plain file: success. */
++ const char *plain = t_strconcat(tmpdir, "/plain2", NULL);
++ int wfd = creat(plain, 0600);
++ if (wfd < 0)
++ i_fatal("creat(%s) failed: %m", plain);
++ i_close_fd(&wfd);
++
++ fd = t_openat_safe_dir(tmpdir, "plain2", O_RDONLY, &error);
++ test_assert(fd >= 0);
++ i_close_fd(&fd);
++
++ /* Nonexistent base_dir. */
++ errno = 0;
++ fd = t_openat_safe_dir(t_strconcat(tmpdir, "/no-such-dir", NULL),
++ "plain2", O_RDONLY, &error);
++ test_assert(fd == -1);
++ test_assert(errno == ENOENT);
++ test_assert(error != NULL);
++
++ /* Absolute symlink in relative part: refused. */
++ const char *abs_link2 = t_strconcat(tmpdir, "/abs-link2", NULL);
++ if (symlink("/etc/hostname", abs_link2) < 0)
++ i_fatal("symlink failed: %m");
++ errno = 0;
++ fd = t_openat_safe_dir(tmpdir, "abs-link2", O_RDONLY, &error);
++ test_assert(fd == -1);
++ test_assert(errno == ELOOP);
++
++ /* O_NOFOLLOW: symlink as base_dir final component refused. */
++ const char *dir_link = t_strconcat(tmpdir, "/dir-link", NULL);
++ if (symlink(tmpdir, dir_link) < 0)
++ i_fatal("symlink failed: %m");
++ errno = 0;
++ fd = t_openat_safe_dir(dir_link, "plain2",
++ O_RDONLY | O_NOFOLLOW, &error);
++ test_assert(fd == -1);
++ /* Without O_NOFOLLOW the same symlinked base_dir succeeds. */
++ fd = t_openat_safe_dir(dir_link, "plain2", O_RDONLY, &error);
++ test_assert(fd >= 0);
++ i_close_fd(&fd);
++
++ i_unlink(plain);
++ i_unlink(abs_link2);
++ i_unlink(dir_link);
++}
++
+ static void test_cleanup(void)
+ {
+ const char *error;
+@@ -396,6 +447,7 @@ void test_path_util(void)
+ test_link_alloc();
+ test_link_alloc2();
+ test_openat_safe();
++ test_openat_safe_dir();
+ test_cleanup();
+ alarm(0);
+ test_end();
--- /dev/null
+From c7629782bfbb0f32076c220230db67bd01d5e115 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 16 Apr 2026 17:38:41 +0200
+Subject: [PATCH 3/5] lib-compression: Add o_stream_deflate_reset_dict()
+
+New public function that locates the deflate ostream in the parent chain
+(transparently handles rawlog wrappers) and schedules a Z_FULL_FLUSH on
+the next uncork/flush. Z_FULL_FLUSH emits all buffered data and then
+resets the deflate dictionary, so subsequent compressed output cannot
+reference data from before the call.
+
+Adds a pending_dict_reset flag to struct zlib_ostream. The flag persists
+through partial flush retries and is cleared only once the full flush
+loop completes.
+---
+ src/lib-compression/ostream-zlib.c | 37 ++++++++++++++++++++++++++++--
+ src/lib-compression/ostream-zlib.h | 7 ++++++
+ 2 files changed, 42 insertions(+), 2 deletions(-)
+
+diff --git a/src/lib-compression/ostream-zlib.c b/src/lib-compression/ostream-zlib.c
+index 6aeccbf35d..5bc41d0b2c 100644
+--- a/src/lib-compression/ostream-zlib.c
++++ b/src/lib-compression/ostream-zlib.c
+@@ -23,6 +23,7 @@ struct zlib_ostream {
+
+ bool gz:1;
+ bool flushed:1;
++ bool pending_dict_reset:1;
+ };
+
+ struct zlib_settings {
+@@ -252,8 +253,14 @@ o_stream_zlib_send_flush(struct zlib_ostream *zstream, bool final)
+ if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
+ return ret;
+
+- flush = final ? Z_FINISH :
+- (!zstream->gz ? Z_SYNC_FLUSH : Z_NO_FLUSH);
++ if (final)
++ flush = Z_FINISH;
++ else if (zstream->gz)
++ flush = Z_NO_FLUSH;
++ else if (zstream->pending_dict_reset)
++ flush = Z_FULL_FLUSH;
++ else
++ flush = Z_SYNC_FLUSH;
+
+ i_assert(zstream->outbuf_used == 0);
+ do {
+@@ -283,6 +290,10 @@ o_stream_zlib_send_flush(struct zlib_ostream *zstream, bool final)
+ }
+ } while (zs->avail_out != sizeof(zstream->outbuf));
+
++ /* Z_FULL_FLUSH completes here; clear the request so subsequent flushes
++ use the normal Z_SYNC_FLUSH again. */
++ zstream->pending_dict_reset = FALSE;
++
+ if (final) {
+ if (o_stream_zlib_send_gz_trailer(zstream) < 0)
+ return -1;
+@@ -443,3 +454,25 @@ struct ostream *o_stream_create_deflate_auto(struct ostream *output, struct even
+ {
+ return o_stream_create_zlib_auto(output, event, FALSE);
+ }
++void o_stream_deflate_reset_dict(struct ostream *_output)
++{
++ struct ostream_private *stream = _output->real_stream;
++
++ /* Traverse the ostream parent chain to find the deflate ostream.
++ The caller may pass a rawlog-wrapped stream or the deflate stream
++ itself; either way we find it by its sendv function pointer. */
++ while (stream != NULL) {
++ if (stream->sendv == o_stream_zlib_sendv) {
++ struct zlib_ostream *zstream =
++ container_of(stream, struct zlib_ostream, ostream);
++ i_assert(!zstream->gz);
++ zstream->pending_dict_reset = TRUE;
++ o_stream_set_flush_pending(
++ &zstream->ostream.ostream, TRUE);
++ return;
++ }
++ stream = (stream->parent != NULL) ?
++ stream->parent->real_stream : NULL;
++ }
++ /* No deflate ostream found in the chain – nothing to reset. */
++}
+diff --git a/src/lib-compression/ostream-zlib.h b/src/lib-compression/ostream-zlib.h
+index 9738e36cc0..4db62fda81 100644
+--- a/src/lib-compression/ostream-zlib.h
++++ b/src/lib-compression/ostream-zlib.h
+@@ -7,4 +7,11 @@ struct ostream *o_stream_create_bz2_auto(struct ostream *output, struct event *e
+ struct ostream *o_stream_create_lz4_auto(struct ostream *output, struct event *event);
+ struct ostream *o_stream_create_zstd_auto(struct ostream *output, struct event *event);
+
++/* Reset the deflate compression dictionary so that subsequent compressed
++ output cannot reference data from before this call. Pass any ostream in
++ the chain (e.g. a rawlog wrapper); the function locates the deflate stream
++ internally. Safe to call when compression is not active – it is a no-op
++ in that case. */
++void o_stream_deflate_reset_dict(struct ostream *output);
++
+ #endif
+--
+2.47.3
+
--- /dev/null
+From cc00fbd91027e9f7b3595ced4c505deec33e7f6c Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Fri, 12 Jun 2026 11:19:42 +0000
+Subject: [PATCH 3/4] lib-compression: istream-zstd - Guard against no-progress
+ loop in read
+
+If ZSTD_decompressStream() returns with neither input consumed nor
+output produced, the read loop would spin indefinitely. Add a check
+after each call: if input.pos is unchanged and output.pos is zero,
+treat it as a corrupt stream (EIO).
+---
+ src/lib-compression/istream-zstd.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+Index: trixie/src/lib-compression/istream-zstd.c
+===================================================================
+--- trixie.orig/src/lib-compression/istream-zstd.c
++++ trixie/src/lib-compression/istream-zstd.c
+@@ -175,12 +175,20 @@ static ssize_t i_stream_zstd_read(struct
+ zstream->output.pos = 0;
+ zstream->output.size = ZSTD_DStreamOutSize();
+
++ size_t old_input_pos = zstream->input.pos;
+ size_t zret = ZSTD_decompressStream(zstream->dstream, &zstream->output,
+ &zstream->input);
+ if (ZSTD_isError(zret) != 0) {
+ i_stream_zstd_read_error(zstream, zret);
+ return -1;
+ }
++ if (zstream->input.pos == old_input_pos && zstream->output.pos == 0) {
++ io_stream_set_error(&zstream->istream.iostream,
++ "zstd.read(%s): decompressor made no progress",
++ i_stream_get_name(&zstream->istream.istream));
++ zstream->istream.istream.stream_errno = EIO;
++ return -1;
++ }
+ /* ZSTD magic number is 4 bytes, but it's only defined after v0.8 */
+ if (!zstream->hdr_read && zstream->input.size > 4)
+ zstream->hdr_read = TRUE;
--- /dev/null
+From a26d37e321c46eb974703ac85bab66cce544d35f Mon Sep 17 00:00:00 2001
+From: Marco Bettini <marco.bettini@open-xchange.com>
+Date: Fri, 10 Apr 2026 08:49:49 +0000
+Subject: [PATCH 3/4] lib-mail: o_stream_dot_sendv() - Use enumeration to
+ define the items to inject
+
+---
+ src/lib-mail/ostream-dot.c | 39 ++++++++++++++++++++++++++------------
+ 1 file changed, 27 insertions(+), 12 deletions(-)
+
+Index: trixie/src/lib-mail/ostream-dot.c
+===================================================================
+--- trixie.orig/src/lib-mail/ostream-dot.c
++++ trixie/src/lib-mail/ostream-dot.c
+@@ -71,6 +71,12 @@ o_stream_dot_close(struct iostream_priva
+ o_stream_close(dstream->ostream.parent);
+ }
+
++enum o_stream_dot_sendv_add {
++ ADD_NONE,
++ ADD_CR,
++ ADD_DOT,
++};
++
+ static ssize_t
+ o_stream_dot_sendv(struct ostream_private *stream,
+ const struct const_iovec *iov, unsigned int iov_count)
+@@ -106,7 +112,7 @@ o_stream_dot_sendv(struct ostream_privat
+ p = data;
+ pend = CONST_PTR_OFFSET(data, size);
+ for (; p < pend && ((size_t)(p - data) + 2) <= max_bytes; p++) {
+- char add = 0;
++ enum o_stream_dot_sendv_add add = ADD_NONE;
+
+ size = pend - p;
+ switch (dstream->state) {
+@@ -122,8 +128,7 @@ o_stream_dot_sendv(struct ostream_privat
+ switch (*p) {
+ case '\n':
+ dstream->state = STREAM_STATE_CRLF;
+- /* add missing CR */
+- add = '\r';
++ add = ADD_CR;
+ break;
+ case '\r':
+ dstream->state = STREAM_STATE_CR;
+@@ -155,12 +160,10 @@ o_stream_dot_sendv(struct ostream_privat
+ break;
+ case '\n':
+ dstream->state = STREAM_STATE_CRLF;
+- /* add missing CR */
+- add = '\r';
++ add = ADD_CR;
+ break;
+ case '.':
+- /* add dot */
+- add = '.';
++ add = ADD_DOT;
+ /* fall through */
+ default:
+ dstream->state = STREAM_STATE_NONE;
+@@ -185,12 +188,24 @@ o_stream_dot_sendv(struct ostream_privat
+ }
+ /* insert byte (substitute one with pair) */
+ data++;
+- iovn.iov_base = (add == '\r' ? "\r\n" : "..");
+- iovn.iov_len = 2;
++
++ switch(add) {
++ case ADD_DOT:
++ iovn.iov_base = "..";
++ iovn.iov_len = 2;
++ break;
++ case ADD_CR:
++ iovn.iov_base = "\r\n";
++ iovn.iov_len = 2;
++ break;
++ default:
++ i_unreached();
++ }
++
+ array_push_back(&iov_arr, &iovn);
+- i_assert(max_bytes >= 2);
+- max_bytes -= 2;
+- added++;
++ i_assert(max_bytes >= iovn.iov_len);
++ max_bytes -= iovn.iov_len;
++ added += iovn.iov_len - 1;
+ sent++;
+ }
+ }
--- /dev/null
+From e0c8bcd5d0d8bbd39edb2bc78cd15f469834660a Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 3 Jun 2026 12:10:46 +0000
+Subject: [PATCH 3/7] lib-sieve: Rename on-disk header rusage fields to
+ unused_*
+
+The .svbin header's resource_usage fields no longer carry data: they
+are zeroed on save and discarded on load. Cumulative CPU tracking
+lives in the per-user sieve-rusage file, and the in-memory carry-over
+lives in sbin->persisted_rusage.
+
+Flatten the nested resource_usage struct and rename the fields to
+unused_update_time and unused_cpu_time_msecs so it is obvious the
+fields are kept only for on-disk format compatibility. The memory
+layout of struct sieve_binary_header is unchanged.
+---
+ src/lib-sieve/sieve-binary-file.c | 8 +++++---
+ src/lib-sieve/sieve-binary-private.h | 9 +++++----
+ 2 files changed, 10 insertions(+), 7 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary-file.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary-file.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary-file.c
+@@ -147,13 +147,14 @@ sieve_binary_file_read_header(struct sie
+ return 0;
+ }
+
+-/* The on-disk binary header carries an unused resource_usage struct for
++/* The on-disk binary header carries unused resource_usage fields for
+ backward compatibility. Tracking lives in the per-user sieve-rusage file. */
+ static void sieve_binary_file_zero_header_rusage(struct sieve_binary *sbin)
+ {
+ struct sieve_binary_header *header = &sbin->header;
+
+- i_zero(&header->resource_usage);
++ header->unused_update_time = 0;
++ header->unused_cpu_time_msecs = 0;
+ sieve_resource_usage_init(&sbin->rusage);
+ sbin->rusage_updated = FALSE;
+ }
+@@ -834,7 +835,8 @@ _sieve_binary_open(struct sieve_binary *
+
+ /* Resource usage tracking is no longer kept in the binary file;
+ discard any data from older versions. */
+- i_zero(&sbin->header.resource_usage);
++ sbin->header.unused_update_time = 0;
++ sbin->header.unused_cpu_time_msecs = 0;
+ sbin->header.flags &= ENUM_NEGATE(SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
+
+ /* Load block index */
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary-private.h
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary-private.h
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary-private.h
+@@ -26,10 +26,11 @@ struct sieve_binary_header {
+ uint32_t hdr_size;
+ uint32_t flags;
+
+- struct {
+- uint64_t update_time;
+- uint32_t cpu_time_msecs;
+- } resource_usage;
++ /* Resource usage tracking moved to per-user sieve-rusage file. These
++ fields are kept zeroed and present only for on-disk format
++ compatibility with older .svbin files. */
++ uint64_t unused_update_time;
++ uint32_t unused_cpu_time_msecs;
+ };
+
+ struct sieve_binary_file {
--- /dev/null
+From 76957df3b7ec3ca682e779fa91e22f6d8bb22740 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 1 May 2026 06:28:33 +0000
+Subject: [PATCH 3/3] lib-sieve: edit_mail_headers_parse() - Fix info leak via
+ NUL-truncated header copy
+
+i_strndup() stops at the first NUL byte, so embedded NULs caused field->data
+to be shorter than field->size. This left stale heap data readable past the
+copied content when callers treat data+body_offset as a string.
+
+Adds a test-edit-mail unit test and a sieve testsuite case that both
+exercise edit_mail_headers_parse() with a NUL byte embedded in a header
+value. The unit test memcmp-verifies that the bytes after the NUL are
+preserved; the sieve test triggers an invalid heap read detectable by
+valgrind under the old code.
+---
+ Makefile.am | 1 +
+ src/lib-sieve/util/edit-mail.c | 8 +-
+ src/lib-sieve/util/test-edit-mail.c | 140 +++++++++++++++++++
+ tests/extensions/editheader/nul-value.svtest | 46 ++++++
+ 4 files changed, 192 insertions(+), 3 deletions(-)
+ create mode 100644 tests/extensions/editheader/nul-value.svtest
+
+Index: trixie/pigeonhole/Makefile.am
+===================================================================
+--- trixie.orig/pigeonhole/Makefile.am
++++ trixie/pigeonhole/Makefile.am
+@@ -169,6 +169,7 @@ test_cases = \
+ tests/extensions/editheader/utf8.svtest \
+ tests/extensions/editheader/protected.svtest \
+ tests/extensions/editheader/errors.svtest \
++ tests/extensions/editheader/nul-value.svtest \
+ tests/extensions/editheader/execute.svtest \
+ tests/extensions/duplicate/errors.svtest \
+ tests/extensions/duplicate/execute.svtest \
+Index: trixie/pigeonhole/src/lib-sieve/util/edit-mail.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/util/edit-mail.c
++++ trixie/pigeonhole/src/lib-sieve/util/edit-mail.c
+@@ -553,8 +553,9 @@ edit_mail_header_field_create(struct edi
+ &field->body_offset);
+
+ /* Copy to new field */
+- field->data = i_strndup(str_data(data), str_len(data));
+ field->size = str_len(data);
++ field->data = i_malloc(field->size + 1);
++ memcpy(field->data, str_data(data), field->size);
+ field->virtual_size = (edmail->crlf ?
+ field->size : field->size + lines);
+ field->lines = lines;
+@@ -831,8 +832,9 @@ static int edit_mail_headers_parse(struc
+
+ field->size = str_len(hdr_data);
+ field->virtual_size = field->size + vsize_diff;
+- field->data = i_strndup(str_data(hdr_data),
+- field->size);
++ field->data = i_malloc(field->size + 1);
++ memcpy(field->data, str_data(hdr_data),
++ field->size);
+ field->offset = offset;
+ field->lines = lines;
+
+Index: trixie/pigeonhole/src/lib-sieve/util/test-edit-mail.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/util/test-edit-mail.c
++++ trixie/pigeonhole/src/lib-sieve/util/test-edit-mail.c
+@@ -866,6 +866,144 @@ static void test_edit_mail_empty(void)
+ test_end();
+ }
+
++static void test_edit_mail_nul_in_header(void)
++{
++ /* Message with a NUL byte embedded in the X-Has-NUL header value.
++ * sizeof() - 1 gives the true byte count, skipping the C string's
++ * trailing NUL terminator, while preserving the embedded \x00. */
++ static const unsigned char message[] =
++ "From: sender@example.com\n"
++ "X-Has-NUL: value\x00" "afterNUL\n"
++ "Subject: Test\n"
++ "\n"
++ "Body\n";
++ /* Expected output after deleting the Subject header. */
++ static const unsigned char expected[] =
++ "From: sender@example.com\n"
++ "X-Has-NUL: value\x00" "afterNUL\n"
++ "\n"
++ "Body\n";
++ struct istream *input_msg, *input_mail;
++ buffer_t *buffer;
++ struct mail_raw *rawmail;
++ struct edit_mail *edmail;
++ struct mail *mail;
++
++ test_begin("edit-mail - NUL byte in header value");
++ test_edit_mail_init();
++
++ /* sizeof - 1 excludes the trailing C-string NUL terminator */
++ input_msg = i_stream_create_from_data(message, sizeof(message) - 1);
++
++ rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
++ edmail = edit_mail_wrap(rawmail->mail);
++
++ /* Deleting any header triggers edit_mail_headers_parse(), which with
++ * the old i_strndup() allocated only strlen("X-Has-NUL: value")+1=17
++ * bytes for X-Has-NUL's field->data even though field->size=26.
++ * Streaming then called memcpy(dst, field->data, 26), reading 9 bytes
++ * past the end of the allocation and producing garbage in place of
++ * "afterNUL\n". */
++ edit_mail_header_delete(edmail, "Subject", 0);
++
++ mail = edit_mail_get_mail(edmail);
++
++ if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
++ i_fatal("Failed to open mail stream: %s",
++ mailbox_get_last_internal_error(mail->box, NULL));
++ }
++
++ buffer = buffer_create_dynamic(default_pool, 128);
++
++ /* normal */
++
++ i_stream_seek(input_mail, 0);
++ test_stream_data(input_mail, buffer);
++
++ test_out("nul in header",
++ buffer->used == sizeof(expected) - 1 &&
++ memcmp(buffer->data, expected, sizeof(expected) - 1) == 0);
++
++ /* slow (byte-by-byte) */
++
++ i_stream_seek(input_mail, 0);
++ buffer_set_used_size(buffer, 0);
++ test_stream_data_slow(input_mail, buffer);
++
++ test_out("nul in header, slow",
++ buffer->used == sizeof(expected) - 1 &&
++ memcmp(buffer->data, expected, sizeof(expected) - 1) == 0);
++
++ /* clean up */
++
++ buffer_free(&buffer);
++ edit_mail_unwrap(&edmail);
++ mail_raw_close(&rawmail);
++ i_stream_unref(&input_msg);
++ test_edit_mail_deinit();
++ test_end();
++}
++
++static void test_edit_mail_empty2(void)
++{
++ struct istream *input_msg, *input_mail;
++ buffer_t *buffer;
++ struct mail_raw *rawmail;
++ struct edit_mail *edmail;
++ struct mail *mail;
++ const char *value;
++
++ test_begin("edit-mail - empty message (delete, add)");
++ test_edit_mail_init();
++
++ /* Compose the message */
++
++ input_msg = i_stream_create_from_data("", 0);
++
++ rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
++
++ edmail = edit_mail_wrap(rawmail->mail);
++
++ /* Delete header */
++
++ edit_mail_header_delete(edmail, "X-B", 0);
++
++ /* Add header */
++
++ edit_mail_header_add(edmail, "X-B", "Frop", TRUE);
++ mail = edit_mail_get_mail(edmail);
++
++ /* Prepare tests */
++
++ if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
++ i_fatal("Failed to open mail stream: %s",
++ mailbox_get_last_error(mail->box, NULL));
++ }
++
++ buffer = buffer_create_dynamic(default_pool, 1024);
++
++ /* Evaluate modified header */
++
++ test_assert(mail_get_first_header_utf8(mail, "X-B", &value) > 0 &&
++ strcmp(value, "Frop") == 0);
++
++ /* Added */
++
++ i_stream_seek(input_mail, 0);
++ buffer_set_used_size(buffer, 0);
++
++ test_stream_data(input_mail, buffer);
++
++ /* Clean up */
++
++ buffer_free(&buffer);
++ edit_mail_unwrap(&edmail);
++ mail_raw_close(&rawmail);
++ i_stream_unref(&input_msg);
++ test_edit_mail_deinit();
++ test_end();
++}
++
+ int main(int argc, char *argv[])
+ {
+ static void (*test_functions[])(void) = {
+@@ -873,6 +1011,8 @@ int main(int argc, char *argv[])
+ test_edit_mail_big_header,
+ test_edit_mail_small_buffer,
+ test_edit_mail_empty,
++ test_edit_mail_empty2,
++ test_edit_mail_nul_in_header,
+ NULL
+ };
+ const enum master_service_flags service_flags =
+Index: trixie/pigeonhole/tests/extensions/editheader/nul-value.svtest
+===================================================================
+--- /dev/null
++++ trixie/pigeonhole/tests/extensions/editheader/nul-value.svtest
+@@ -0,0 +1,46 @@
++require "vnd.dovecot.testsuite";
++require "encoded-character";
++require "variables";
++require "fileinto";
++require "mailbox";
++
++require "editheader";
++
++/*
++ * Regression test: edit_mail_headers_parse() used i_strndup() to copy the
++ * raw header data, which stopped at the first NUL byte and allocated a buffer
++ * shorter than field->size. When the modified message was subsequently
++ * streamed (e.g. fileinto), memcpy() read field->size bytes out of that
++ * under-sized buffer, causing an invalid heap read detectable by valgrind.
++ */
++
++/* Construct a raw message that has a NUL byte inside a header value.
++ * ${hex:00} = NUL. field->size covers the full header line including
++ * bytes after the NUL; the old i_strndup() only allocated up to the NUL. */
++test_set "message" text:
++From: sender@example.com
++X-Has-NUL: value${hex:00}afterNUL
++Subject: Test
++
++Body
++.
++;
++
++test "editheader - NUL byte in header value" {
++ /* deleteheader calls edit_mail_headers_parse(), which allocates
++ * field->data for every header including X-Has-NUL. With the old
++ * i_strndup() code, X-Has-NUL's allocation is only strlen("value")+1
++ * bytes, but field->size covers the full extent including bytes after
++ * the NUL. deleteheader also sets modified=TRUE. */
++ deleteheader "Subject";
++
++ fileinto :create "nul-test";
++
++ /* Streaming iterates over all remaining parsed headers including
++ * X-Has-NUL, reading field->size bytes from field->data. With the
++ * old under-sized allocation this is an invalid heap read that
++ * valgrind detects. */
++ if not test_result_execute {
++ test_fail "failed to execute result";
++ }
++}
--- /dev/null
+From 12fcbfe65431ece3f49d39d553f22001b3b007b6 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 7 May 2026 10:57:59 +0000
+Subject: [PATCH 3/5] lib-smtp: smtp-server - Expose
+ smtp_server_connection_reply_immediate
+
+The function already existed internally as a way to write a reply directly
+to the output stream, bypassing the per-command reply queue. Make it part
+of the public API so callers that need a reply on the wire before tearing
+the connection down (where queued replies would otherwise be aborted) can
+use it.
+---
+ src/lib-smtp/smtp-server-private.h | 3 ---
+ src/lib-smtp/smtp-server.h | 10 ++++++++++
+ 2 files changed, 10 insertions(+), 3 deletions(-)
+
+Index: trixie/src/lib-smtp/smtp-server-private.h
+===================================================================
+--- trixie.orig/src/lib-smtp/smtp-server-private.h
++++ trixie/src/lib-smtp/smtp-server-private.h
+@@ -323,9 +323,6 @@ void smtp_server_connection_reply_lines(
+ unsigned int status,
+ const char *enh_code,
+ const char *const *text_lines);
+-void smtp_server_connection_reply_immediate(
+- struct smtp_server_connection *conn, unsigned int status,
+- const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+ void smtp_server_connection_reset_state(struct smtp_server_connection *conn);
+ void smtp_server_connection_set_state(struct smtp_server_connection *conn,
+Index: trixie/src/lib-smtp/smtp-server.h
+===================================================================
+--- trixie.orig/src/lib-smtp/smtp-server.h
++++ trixie/src/lib-smtp/smtp-server.h
+@@ -477,6 +477,16 @@ void smtp_server_connection_set_ssl_stre
+ void smtp_server_connection_close(struct smtp_server_connection **_conn,
+ const char *reason) ATTR_NULL(2);
+ bool smtp_server_connection_is_closed(struct smtp_server_connection *conn);
++/* Send a reply line directly to the output stream, bypassing the per-command
++ reply queue. Useful when the caller is about to close the connection and
++ needs the reply on the wire before close/destroy aborts queued replies.
++ The formatted text must be a single line (no CR/LF); unlike
++ smtp_server_reply_add_text(), this function does not split on \n nor add
++ the SMTP continuation prefix to subsequent lines. */
++void smtp_server_connection_reply_immediate(struct smtp_server_connection *conn,
++ unsigned int status,
++ const char *fmt, ...)
++ ATTR_FORMAT(3, 4);
+ void smtp_server_connection_terminate(struct smtp_server_connection **_conn,
+ const char *enh_code, const char *reason)
+ ATTR_NULL(3);
--- /dev/null
+From 3c388ec11db1d61a2795241321d80a7dc245105c Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Tue, 2 Jun 2026 09:54:41 +0000
+Subject: [PATCH 3/6] lib-sql: Add sql_commit_schedule_delayed() helper
+
+---
+ src/lib-sql/sql-api.c | 21 ++++++++++++++-------
+ 1 file changed, 14 insertions(+), 7 deletions(-)
+
+Index: trixie/src/lib-sql/sql-api.c
+===================================================================
+--- trixie.orig/src/lib-sql/sql-api.c
++++ trixie/src/lib-sql/sql-api.c
+@@ -885,6 +885,19 @@ static void sql_commit_delayed_callback(
+ i_free(cb);
+ }
+
++static void
++sql_commit_schedule_delayed(struct sql_db *db, const char *error,
++ sql_commit_callback_t *callback, void *context)
++{
++ struct sql_commit_result_delayed *cb = i_new(struct sql_commit_result_delayed, 1);
++ cb->db = db;
++ cb->error = i_strdup(error);
++ cb->callback = callback;
++ cb->context = context;
++ cb->to = timeout_add_short(0, sql_commit_delayed_callback, cb);
++ DLLIST_PREPEND(&db->commit_delayed_list, cb);
++}
++
+ #undef sql_transaction_commit
+ void sql_transaction_commit(struct sql_transaction_context **_ctx,
+ sql_commit_callback_t *callback, void *context)
+@@ -898,15 +911,9 @@ void sql_transaction_commit(struct sql_t
+ return;
+ }
+
+- struct sql_commit_result_delayed *cb = i_new(struct sql_commit_result_delayed, 1);
+ const char *error = NULL;
+ ctx->db->v.transaction_commit_s(ctx, &error);
+- cb->db = db;
+- cb->error = i_strdup(error);
+- cb->callback = callback;
+- cb->context = context;
+- cb->to = timeout_add_short(0, sql_commit_delayed_callback, cb);
+- DLLIST_PREPEND(&db->commit_delayed_list, cb);
++ sql_commit_schedule_delayed(db, error, callback, context);
+ }
+
+ int sql_transaction_commit_s(struct sql_transaction_context **_ctx,
--- /dev/null
+From e6934c6d1afdcecd63591d7f514ccb39e11d6ada Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 17 Apr 2026 13:15:54 +0000
+Subject: [PATCH 3/3] lib-storage: thread - Limit ancestor chain traversal
+ depth to prevent O(N^2) CPU usage
+
+The per-message References limit (MAIL_THREAD_REFERENCES_MAX) prevents a
+single crafted message from causing O(N^2) traversals in
+thread_node_has_ancestor(). However, multiple crafted messages each
+containing 1000 References entries can build an arbitrarily deep ancestor
+chain across the mailbox, causing the same quadratic blowup spread over
+many messages: processing email k costs O(k * MAIL_THREAD_REFERENCES_MAX)
+steps, giving O(M^2 * MAIL_THREAD_REFERENCES_MAX) total for M emails.
+
+Fix this by limiting the traversal depth in thread_node_has_ancestor() to
+MAIL_THREAD_REFERENCES_MAX steps. When the limit is reached the link is
+dropped, bounding per-email work to O(MAIL_THREAD_REFERENCES_MAX^2)
+regardless of how deep the chain was built by prior messages.
+---
+ src/lib-storage/index/index-thread-links.c | 31 +++++++++++++++++++---
+ 1 file changed, 28 insertions(+), 3 deletions(-)
+
+diff --git a/src/lib-storage/index/index-thread-links.c b/src/lib-storage/index/index-thread-links.c
+index 9affb8338d..0ce33a0772 100644
+--- a/src/lib-storage/index/index-thread-links.c
++++ b/src/lib-storage/index/index-thread-links.c
+@@ -31,12 +31,24 @@ static uint32_t thread_msg_add(struct mail_thread_cache *cache,
+
+ static bool thread_node_has_ancestor(struct mail_thread_cache *cache,
+ const struct mail_thread_node *node,
+- const struct mail_thread_node *ancestor)
++ const struct mail_thread_node *ancestor,
++ bool *depth_exceeded_r)
+ {
++ unsigned int n = 0;
++
++ *depth_exceeded_r = FALSE;
+ while (node != ancestor) {
+ if (node->parent_idx == 0)
+ return FALSE;
+-
++ if (++n > MAIL_THREAD_REFERENCES_MAX) {
++ /* Ancestor chain is deeper than the per-message
++ References limit. Multiple crafted messages can build
++ an arbitrarily deep chain, causing O(N^2) traversals
++ per email even with the per-message limit. Treat the
++ link as unsafe to prevent the DoS. */
++ *depth_exceeded_r = TRUE;
++ return FALSE;
++ }
+ node = array_idx(&cache->thread_nodes, node->parent_idx);
+ }
+ return TRUE;
+@@ -47,6 +59,7 @@ static void thread_link_reference(struct mail_thread_cache *cache,
+ {
+ struct mail_thread_node *node, *parent, *child;
+ uint32_t idx;
++ bool depth_exceeded;
+
+ i_assert(parent_idx < cache->first_invalid_msgid_str_idx);
+
+@@ -62,7 +75,7 @@ static void thread_link_reference(struct mail_thread_cache *cache,
+ }
+
+ child->parent_link_refcount++;
+- if (thread_node_has_ancestor(cache, parent, child)) {
++ if (thread_node_has_ancestor(cache, parent, child, &depth_exceeded)) {
+ if (parent == child) {
+ /* loops to itself - ignore */
+ return;
+@@ -88,6 +101,18 @@ static void thread_link_reference(struct mail_thread_cache *cache,
+ node->child_unref_rebuilds = TRUE;
+ } while (node != child);
+ return;
++ } else if (depth_exceeded) {
++ /* Ancestor chain exceeded the depth limit; drop this link.
++ parent_link_refcount was already incremented above and is
++ intentionally kept that way: mail_thread_unref_link()
++ walks References blindly at remove time and would hit
++ i_assert(child->parent_link_refcount > 0) on the matching
++ edge if we hadn't bumped it here. The trade-off is that
++ the bumped refcount can leave child->parent_idx attached
++ to a stale parent if another message later sets a real
++ parent edge on this child and is then expunged - threading
++ stays wrong until some other event triggers a rebuild. */
++ return;
+ } else if (child->parent_idx == parent_idx) {
+ /* The same link already exists */
+ return;
+--
+2.47.3
+
--- /dev/null
+From a5d06e714ed25a6cb18fd3af99314e94fa080046 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 3 Jun 2026 18:16:38 +0300
+Subject: [PATCH 4/5] acl: dovecot-acl-list is never supposed to be a symlink -
+ use O_NOFOLLOW flag
+
+---
+ src/plugins/acl/acl-backend-vfile-acllist.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/plugins/acl/acl-backend-vfile-acllist.c b/src/plugins/acl/acl-backend-vfile-acllist.c
+index 7dfcfdd393..d291b55594 100644
+--- a/src/plugins/acl/acl-backend-vfile-acllist.c
++++ b/src/plugins/acl/acl-backend-vfile-acllist.c
+@@ -115,7 +115,7 @@ static int acl_backend_vfile_acllist_read(struct acl_backend_vfile *backend)
+ return 0;
+ }
+
+- fd = open(path, O_RDONLY);
++ fd = open(path, O_RDONLY | O_NOFOLLOW);
+ if (fd == -1) {
+ if (errno == ENOENT) {
+ backend->acllist_mtime = 0;
+--
+2.39.5
+
--- /dev/null
+From da4c52ac021b32bfc9f926f3c229c91ecb729cca Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 16 Apr 2026 17:38:45 +0200
+Subject: [PATCH 4/5] imap-login: Add dict_reset side-channel command to
+ imap-proxy
+
+When imap_compress_on_proxy is enabled, DEFLATE compression runs inside
+the imap-login process. The imap backend process cannot call
+o_stream_deflate_reset_dict() directly on a remote stream, so this adds
+a new side-channel command "dict_reset" that the backend can send to
+trigger the dictionary reset on the proxy side.
+---
+ src/imap-login/imap-proxy.c | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+diff --git a/src/imap-login/imap-proxy.c b/src/imap-login/imap-proxy.c
+index 03dad690e1..2084cb290d 100644
+--- a/src/imap-login/imap-proxy.c
++++ b/src/imap-login/imap-proxy.c
+@@ -10,6 +10,7 @@
+ #include "str-sanitize.h"
+ #include "safe-memset.h"
+ #include "compression.h"
++#include "ostream-zlib.h"
+ #include "dsasl-client.h"
+ #include "imap-login-client.h"
+ #include "client-authenticate.h"
+@@ -641,11 +642,24 @@ proxy_side_cmd_compress(struct client *client, const char *const *args,
+ return 0;
+ }
+
++static int
++proxy_side_cmd_dict_reset(struct client *client,
++ const char *const *args ATTR_UNUSED,
++ const char **error_r ATTR_UNUSED)
++{
++ struct ostream *client_output =
++ login_proxy_get_client_ostream(client->login_proxy);
++ o_stream_deflate_reset_dict(client_output);
++ return 0;
++}
++
+ int imap_proxy_side_channel_input(struct client *client,
+ const char *const *args, const char **error_r)
+ {
+ if (strcmp(args[0], "compress") == 0)
+ return proxy_side_cmd_compress(client, args + 1, error_r);
++ else if (strcmp(args[0], "dict_reset") == 0)
++ return proxy_side_cmd_dict_reset(client, args + 1, error_r);
+ else {
+ *error_r = "Unsupported command";
+ return -1;
+--
+2.47.3
+
--- /dev/null
+From b9afacaf42e7b2efff769dbc3eb10ac48934462a Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 13 Apr 2026 12:38:50 +0200
+Subject: [PATCH 04/12] lib: Key str_hash/strcase_hash with a random seed to
+ prevent HashDoS
+
+hash_init() fills a process-wide uint64_t hash_iv via random_fill()
+at lib_init() time (after random_init()). str_hash() and strcase_hash()
+pass hash_iv as the xxh64 seed so an attacker cannot predict bucket
+placement and manufacture collision chains.
+---
+ src/lib/hash.c | 12 ++++++++++--
+ src/lib/hash.h | 10 +++++++++-
+ src/lib/lib.c | 2 ++
+ 3 files changed, 21 insertions(+), 3 deletions(-)
+
+diff --git a/src/lib/hash.c b/src/lib/hash.c
+index 61fe86ed8e..83eeed42f6 100644
+--- a/src/lib/hash.c
++++ b/src/lib/hash.c
+@@ -5,6 +5,7 @@
+ #include "lib.h"
+ #include "hash.h"
+ #include "primes.h"
++#include "randgen.h"
+ #include "xxh64.h"
+
+ #include <ctype.h>
+@@ -59,6 +60,8 @@ enum hash_table_operation{
+ HASH_TABLE_OP_RESIZE
+ };
+
++uint64_t hash_iv;
++
+ static bool hash_table_resize(struct hash_table *table, bool grow);
+
+ void hash_table_create(struct hash_table **table_r, pool_t node_pool,
+@@ -520,9 +523,14 @@ void hash_table_copy(struct hash_table *dest, struct hash_table *src)
+ hash_table_thaw(dest);
+ }
+
++void hash_init(void)
++{
++ random_fill(&hash_iv, sizeof(hash_iv));
++}
++
+ unsigned int str_hash(const char *p)
+ {
+- return xxh64_to_32(xxh64_data(p, strlen(p), 0));
++ return xxh64_to_32(xxh64_data(p, strlen(p), hash_iv));
+ }
+
+ unsigned int str_stable_hash(const char *p)
+@@ -535,7 +543,7 @@ unsigned int strcase_hash(const char *p)
+ struct xxh64_context ctx;
+ unsigned char c;
+
+- xxh64_init(&ctx, 0);
++ xxh64_init(&ctx, hash_iv);
+ while (*p != '\0') {
+ c = (unsigned char)i_toupper(*p++);
+ xxh64_loop(&ctx, &c, 1);
+diff --git a/src/lib/hash.h b/src/lib/hash.h
+index 84d9c52aef..534c25f6ee 100644
+--- a/src/lib/hash.h
++++ b/src/lib/hash.h
+@@ -14,6 +14,9 @@ typedef unsigned int hash_callback_t(const void *p);
+ /* Returns 0 if the pointers are equal. */
+ typedef int hash_cmp_callback_t(const void *p1, const void *p2);
+
++/* Random per-process IV to use for hash functions */
++extern uint64_t hash_iv;
++
+ /* Create a new hash table. If initial_size is 0, the default value is used.
+ table_pool is used to allocate/free large hash tables, node_pool is used
+ for smaller allocations and can also be alloconly pool. The pools must not
+@@ -168,9 +171,12 @@ void hash_table_copy(struct hash_table *dest, struct hash_table *src);
+ #define hash_table_copy(table1, table2) \
+ hash_table_copy((table1)._table, (table2)._table)
+
+-/* hash function for strings */
++/* Hash function for strings. These are safe against collision attacks. They
++ are initialized with a per-process random key. */
+ unsigned int str_hash(const char *p) ATTR_PURE;
+ unsigned int strcase_hash(const char *p) ATTR_PURE;
++/* Like str_hash(), but there is no per-process random key. This isn't as safe
++ against collision attacks. */
+ unsigned int str_stable_hash(const char *p) ATTR_PURE;
+
+ /* fast hash function which uppercases a-z. Does not work well
+@@ -181,4 +187,6 @@ unsigned int strfastcase_hash(const char *p) ATTR_PURE;
+ /* a generic hash for a given memory block */
+ unsigned int mem_hash(const void *p, unsigned int size) ATTR_PURE;
+
++void hash_init(void);
++
+ #endif
+diff --git a/src/lib/lib.c b/src/lib/lib.c
+index 4df939b7a1..4a4372f516 100644
+--- a/src/lib/lib.c
++++ b/src/lib/lib.c
+@@ -5,6 +5,7 @@
+ #include "array.h"
+ #include "event-filter.h"
+ #include "env-util.h"
++#include "hash.h"
+ #include "hostpid.h"
+ #include "ipwd.h"
+ #include "process-title.h"
+@@ -184,6 +185,7 @@ void lib_init(void)
+ {
+ i_assert(!lib_initialized);
+ random_init();
++ hash_init();
+ data_stack_init();
+ hostpid_init();
+ lib_open_non_stdio_dev_null();
+--
+2.47.3
+
--- /dev/null
+From 32ef85eff6329395b74e47ec69bf7926c629d7c6 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Fri, 12 Jun 2026 11:19:42 +0000
+Subject: [PATCH 4/4] lib-compression: test - Add zero-len frame test for all
+ handlers
+
+Verify that successive empty compressed frames produce a clean EOF
+across all compression algorithms. lz4 is limited to one frame as it
+uses a custom single-stream format without concatenation support.
+---
+ src/lib-compression/test-compression.c | 41 ++++++++++++++++++++++++++
+ 1 file changed, 41 insertions(+)
+
+Index: trixie/src/lib-compression/test-compression.c
+===================================================================
+--- trixie.orig/src/lib-compression/test-compression.c
++++ trixie/src/lib-compression/test-compression.c
+@@ -155,6 +155,46 @@ test_compression_handler_empty(const str
+ }
+
+ static void
++test_compression_handler_zero_frame(const struct compression_handler *handler,
++ bool autodetect)
++{
++ test_begin(t_strdup_printf("compression handler %s (zero-len frame, autodetect=%s)",
++ handler->name, autodetect ? "yes" : "no"));
++
++ /* Produce successive empty frames. Each frame needs its own ostream
++ because o_stream_finish() propagates to the parent; compress each
++ into a separate buffer then concatenate.
++ lz4 uses a custom single-stream format and does not support
++ concatenated frames, so limit it to one. */
++ unsigned int n_frames = strcmp(handler->name, "lz4") == 0 ? 1 : 3;
++ buffer_t *compressed = buffer_create_dynamic(pool_datastack_create(), 256);
++ for (unsigned int i = 0; i < n_frames; i++) {
++ buffer_t *frame_buf = buffer_create_dynamic(pool_datastack_create(), 64);
++ struct ostream *os = test_ostream_create(frame_buf);
++ struct ostream *output = handler->create_ostream_auto(os, set.event);
++ o_stream_unref(&os);
++ test_assert_idx(o_stream_finish(output) == 1, i);
++ o_stream_unref(&output);
++ test_assert_idx(frame_buf->used > 0, i);
++ buffer_append(compressed, frame_buf->data, frame_buf->used);
++ }
++
++ /* Decompress: must yield clean EOF with no error and no loop. */
++ struct istream *is = test_istream_create_data(compressed->data, compressed->used);
++ is->blocking = TRUE;
++ struct istream *input = !autodetect ? handler->create_istream(is) :
++ i_stream_create_decompress(is, 0);
++ i_stream_unref(&is);
++
++ test_assert(i_stream_read(input) == -1);
++ test_assert(input->eof);
++ test_assert(input->stream_errno == 0);
++ i_stream_unref(&input);
++
++ test_end();
++}
++
++static void
+ test_compression_handler_seek(const struct compression_handler *handler,
+ bool autodetect)
+ {
+@@ -743,6 +783,7 @@ static void test_compression_int(bool au
+ test_compression_handler_detect(&compression_handlers[i]);
+ test_compression_handler_short(&compression_handlers[i], autodetect);
+ test_compression_handler_empty(&compression_handlers[i], autodetect);
++ test_compression_handler_zero_frame(&compression_handlers[i], autodetect);
+ test_compression_handler(&compression_handlers[i], autodetect);
+ test_compression_handler_seek(&compression_handlers[i], autodetect);
+ test_compression_handler_reset(&compression_handlers[i], autodetect);
--- /dev/null
+From afb5fd924bf39dc790a573151e2d2626a63d0aec Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 16 Apr 2026 22:38:02 +0200
+Subject: [PATCH 04/14] lib-mail: message-parser-from-parts: Enforce 50 MB
+ all-headers-max-size limit
+
+The preparsed (from-parts) header parser was not tracking
+all_headers_total_size and never called message_parse_header_lower_limit(),
+so the cumulative 50 MB header size limit was never applied when re-parsing
+a message using cached part structure.
+
+Fix by mirroring the same tracking that message-parser.c does in its
+parse_next_header_block(): update all_headers_total_size for each parsed
+header line, and call message_parse_header_lower_limit() with the remaining
+budget when initialising the per-part header parser.
+
+Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
+---
+ src/lib-mail/message-parser-from-parts.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/src/lib-mail/message-parser-from-parts.c b/src/lib-mail/message-parser-from-parts.c
+index 867a916c1c..fb75dc7148 100644
+--- a/src/lib-mail/message-parser-from-parts.c
++++ b/src/lib-mail/message-parser-from-parts.c
+@@ -303,6 +303,11 @@ static int preparsed_parse_next_header(struct message_parser_ctx *ctx,
+ }
+
+ if (hdr != NULL) {
++ if (!hdr->continues) {
++ ctx->all_headers_total_size += hdr->name_len;
++ ctx->all_headers_total_size += hdr->middle_len;
++ }
++ ctx->all_headers_total_size += hdr->value_len;
+ block_r->hdr = hdr;
+ block_r->size = 0;
+ return 1;
+@@ -344,6 +349,11 @@ static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
+ message_parse_header_init(hdr_input, NULL, ctx->hdr_flags);
+ i_stream_unref(&hdr_input);
+
++ size_t headers_available =
++ ctx->all_headers_max_size > ctx->all_headers_total_size ?
++ ctx->all_headers_max_size - ctx->all_headers_total_size : 0;
++ message_parse_header_lower_limit(ctx->hdr_parser_ctx, headers_available);
++
+ ctx->parse_next_block = preparsed_parse_next_header;
+ return preparsed_parse_next_header(ctx, block_r);
+ }
+--
+2.47.3
+
--- /dev/null
+From dc1f33030101d90fcb77612eb3a111854525506a Mon Sep 17 00:00:00 2001
+From: Marco Bettini <marco.bettini@open-xchange.com>
+Date: Wed, 8 Apr 2026 10:14:08 +0000
+Subject: [PATCH 4/4] lib-mail: o_stream_dot_sendv() - Do not send unguarded
+ '.' after bare '\r'
+
+---
+ src/lib-mail/ostream-dot.c | 40 ++++++++++++++++--
+ src/lib-mail/test-iostream-dot.c | 9 ++++
+ src/lib-mail/test-ostream-dot.c | 71 ++++++++++++++++++++++++++++++--
+ 3 files changed, 112 insertions(+), 8 deletions(-)
+
+Index: trixie/src/lib-mail/ostream-dot.c
+===================================================================
+--- trixie.orig/src/lib-mail/ostream-dot.c
++++ trixie/src/lib-mail/ostream-dot.c
+@@ -71,10 +71,12 @@ o_stream_dot_close(struct iostream_priva
+ o_stream_close(dstream->ostream.parent);
+ }
+
++#define ADD_MAX 3
+ enum o_stream_dot_sendv_add {
+ ADD_NONE,
+ ADD_CR,
+ ADD_DOT,
++ ADD_LF_DOT,
+ };
+
+ static ssize_t
+@@ -111,14 +113,14 @@ o_stream_dot_sendv(struct ostream_privat
+
+ p = data;
+ pend = CONST_PTR_OFFSET(data, size);
+- for (; p < pend && ((size_t)(p - data) + 2) <= max_bytes; p++) {
++ for (; p < pend && ((size_t)(p - data) + ADD_MAX) <= max_bytes; p++) {
+ enum o_stream_dot_sendv_add add = ADD_NONE;
+
+ size = pend - p;
+ switch (dstream->state) {
+ /* none */
+ case STREAM_STATE_NONE: {
+- size_t maxlen = I_MIN(size, max_bytes - ((size_t)(p - data) + 2));
++ size_t maxlen = I_MIN(size, max_bytes - ((size_t)(p - data) + ADD_MAX));
+ p = CONST_PTR_OFFSET(p, i_memcspn(p, maxlen, "\r\n", 2));
+ i_assert(p <= pend);
+ if (p == pend) {
+@@ -145,6 +147,9 @@ o_stream_dot_sendv(struct ostream_privat
+ case '\n':
+ dstream->state = STREAM_STATE_CRLF;
+ break;
++ case '.':
++ add = ADD_LF_DOT;
++ /* fall through */
+ default:
+ dstream->state = STREAM_STATE_NONE;
+ break;
+@@ -186,9 +191,31 @@ o_stream_dot_sendv(struct ostream_privat
+ max_bytes -= chunk;
+ sent += chunk;
+ }
+- /* insert byte (substitute one with pair) */
+ data++;
+
++ /* Apply the modifications required according to RFC 5321 to the
++ stream :
++
++ ADD_DOT - Apply standard SMTP dot-stuffing
++
++ ADD_CR - Convert invalid lone LFs to CRLF to comply with
++ DATA line termination requirements, and avoid
++ potentially emitting bare "\n." sequences (same
++ threat as below for "\r." sequences).
++
++ ADD_LF_DOT - Guard against invalid "\r." sequences.
++ A downstream MTA that incorrectly handles bare CRs
++ as line terminators could interpret a '.' at the
++ start of the next line as end-of-DATA. This would
++ allow any following bytes to be processed as SMTP
++ commands.
++
++ ADD_CR and ADD_LF_DOT make the istream-dot round-trip lossy,
++ as these introduce bytes not present in the original input.
++ We accept this tradeoff, since preserving exact byte fidelity
++ for malformed input is less important than eliminating an
++ injection vector. */
++
+ switch(add) {
+ case ADD_DOT:
+ iovn.iov_base = "..";
+@@ -198,12 +225,17 @@ o_stream_dot_sendv(struct ostream_privat
+ iovn.iov_base = "\r\n";
+ iovn.iov_len = 2;
+ break;
++ case ADD_LF_DOT:
++ iovn.iov_base = "\n..";
++ iovn.iov_len = 3;
++ break;
+ default:
+ i_unreached();
+ }
+
+ array_push_back(&iov_arr, &iovn);
+- i_assert(max_bytes >= iovn.iov_len);
++ i_assert(iovn.iov_len <= ADD_MAX);
++ i_assert(iovn.iov_len <= max_bytes);
+ max_bytes -= iovn.iov_len;
+ added += iovn.iov_len - 1;
+ sent++;
+Index: trixie/src/lib-mail/test-ostream-dot.c
+===================================================================
+--- trixie.orig/src/lib-mail/test-ostream-dot.c
++++ trixie/src/lib-mail/test-ostream-dot.c
+@@ -57,6 +57,9 @@ static void test_ostream_dot(void)
+ { "foo\n.\n", "foo\r\n..\r\n.\r\n" },
+ { ".foo\r\n.\r\nfoo\r\n", "..foo\r\n..\r\nfoo\r\n.\r\n" },
+ { ".foo\n.\nfoo\n", "..foo\r\n..\r\nfoo\r\n.\r\n" },
++ { ".", "..\r\n.\r\n" },
++ { "\r.", "\r\n..\r\n.\r\n" },
++ { "\r\r.", "\r\r\n..\r\n.\r\n" },
+ { "\r\n", "\r\n.\r\n" },
+ { "\n", "\r\n.\r\n" },
+ { "", "\r\n.\r\n" },
+@@ -92,7 +95,7 @@ static void test_ostream_dot_parent_almo
+ test_end();
+ }
+
+-static void test_ostream_dot_parent_exact_fit(void)
++static void test_ostream_dot_parent_max_bytes_boundary(void)
+ {
+ buffer_t *output_data;
+ struct ostream *test_output, *output;
+@@ -100,8 +103,8 @@ static void test_ostream_dot_parent_exac
+
+ test_begin("dot ostream parent exact fit");
+ output_data = t_buffer_create(1024);
+- test_output = test_ostream_create_nonblocking(output_data, 2);
+- test_ostream_set_max_output_size(test_output, 2);
++ test_output = test_ostream_create_nonblocking(output_data, 3);
++ test_ostream_set_max_output_size(test_output, 3);
+
+ output = o_stream_create_dot(test_output, FALSE);
+ ret = o_stream_send(output, ".", 1);
+@@ -114,12 +117,72 @@ static void test_ostream_dot_parent_exac
+ test_end();
+ }
+
++/* STATE_CR must persist across sendv calls so that a bare CR ending one send
++ followed by '.' starting the next still triggers ADD_LF_DOT.
++ A stateless per-call implementation would emit the '.' unguarded and let a
++ downstream MTA that treats bare CR as EOL interpret it as end-of-DATA. */
++static void test_ostream_dot_cr_across_sendv(void)
++{
++ buffer_t *output_data;
++ struct ostream *test_output, *output;
++ ssize_t ret;
++ const char *expected = "\r\n..\r\n.\r\n";
++
++ test_begin("dot ostream CR across sendv calls");
++ output_data = t_buffer_create(1024);
++ test_output = o_stream_create_buffer(output_data);
++
++ output = o_stream_create_dot(test_output, FALSE);
++ ret = o_stream_send(output, "\r", 1);
++ test_assert(ret == 1);
++ ret = o_stream_send(output, ".", 1);
++ test_assert(ret == 1);
++ test_assert(o_stream_finish(output) > 0);
++
++ o_stream_unref(&output);
++ o_stream_unref(&test_output);
++
++ test_assert_ucmp(str_len(output_data), ==, strlen(expected));
++ test_assert_memcmp(str_c(output_data), str_len(output_data),
++ expected, strlen(expected));
++ test_end();
++}
++
++/* Off-by-one regression guard for the bare-CR + '.' injection.
++ Input "\r." places '.' at offset 1; the ADD_LF_DOT pair injects 3 bytes
++ ("\n.."). The loop guard check becomes (1 + 3) <= max_bytes, so max_bytes==4
++ is the exact-fit case. */
++static void test_ostream_dot_cr_dot_exact_fit(void)
++{
++ buffer_t *output_data;
++ struct ostream *test_output, *output;
++ ssize_t ret;
++
++ test_begin("dot ostream CR+dot exact fit");
++ output_data = t_buffer_create(1024);
++ test_output = test_ostream_create_nonblocking(output_data, 4);
++ test_ostream_set_max_output_size(test_output, 4);
++
++ output = o_stream_create_dot(test_output, FALSE);
++ ret = o_stream_send(output, "\r.", 2);
++ test_assert(ret == 2);
++ test_assert_ucmp(output_data->used, ==, 4);
++ test_assert_memcmp(output_data->data, output_data->used,
++ "\r\n..", 4);
++
++ o_stream_unref(&output);
++ o_stream_unref(&test_output);
++ test_end();
++}
++
+ int main(void)
+ {
+ static void (*const test_functions[])(void) = {
+ test_ostream_dot,
+ test_ostream_dot_parent_almost_full,
+- test_ostream_dot_parent_exact_fit,
++ test_ostream_dot_parent_max_bytes_boundary,
++ test_ostream_dot_cr_across_sendv,
++ test_ostream_dot_cr_dot_exact_fit,
+ NULL
+ };
+ return test_run(test_functions);
--- /dev/null
+From 7a55953924c54dc116150ae809b976456977db75 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 5 May 2026 16:30:23 +0000
+Subject: [PATCH 4/7] lib-sieve: Switch sieve_max_cpu_time enforcement to
+ time-decayed re-enable
+
+When a user's cumulative CPU time exceeded sieve_max_cpu_time, the
+script was permanently disabled. SIEVE_BINARY_FLAG_RESOURCE_LIMIT was
+set in the binary header on first overrun and was never cleared by
+the resource_usage_timeout decay (which only zeroed the cpu_time
+field), so sieve_binary_check_executable() refused execution forever
+unless the binary was recompiled or an admin manually wiped the file.
+
+Replace the sticky-flag model with one driven purely by the persisted
+cumulative cpu_time, decayed by resource_usage_timeout:
+
+ - Drop SIEVE_BINARY_FLAG_RESOURCE_LIMIT and the enum that defined it;
+ nothing reads sbin->header.flags anymore.
+ - sieve_binary_check_executable() and sieve_binary_check_resource_usage()
+ call sieve_resource_usage_is_excessive() on the current cumulative
+ cpu_time. Raising sieve_max_cpu_time now correctly re-enables a
+ previously over-limit user without further intervention.
+ - Gate per-user file persistence on the per-run delta, not the
+ cumulative total. A refused-without-running attempt has a zero
+ delta, skips the file write, and leaves update_time at the moment
+ work was actually consumed. After resource_usage_timeout seconds
+ of inactivity (or only-trivial deliveries) the next load decays
+ the cpu_time to zero and the user is allowed to run again.
+
+The on-disk per-user sieve-rusage file format becomes:
+
+ V1 <cpu-secs> <update-time>
+---
+ src/lib-sieve/sieve-binary-file.c | 28 ++++++++++++++--------------
+ src/lib-sieve/sieve-binary-private.h | 4 ----
+ src/lib-sieve/sieve-binary.c | 13 ++-----------
+ src/lib-sieve/sieve-rusage.c | 25 ++++++++-----------------
+ src/lib-sieve/sieve-rusage.h | 14 +++++---------
+ 5 files changed, 29 insertions(+), 55 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary-file.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary-file.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary-file.c
+@@ -315,7 +315,7 @@ sieve_binary_save_to_stream(struct sieve
+ header->blocks = blk_count;
+ header->hdr_size = sizeof(*header);
+
+- header->flags &= ENUM_NEGATE(SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
++ header->flags = 0;
+ sieve_binary_file_zero_header_rusage(sbin);
+
+ if (!_save_aligned(sbin, stream, header, sizeof(*header), NULL)) {
+@@ -837,7 +837,7 @@ _sieve_binary_open(struct sieve_binary *
+ discard any data from older versions. */
+ sbin->header.unused_update_time = 0;
+ sbin->header.unused_cpu_time_msecs = 0;
+- sbin->header.flags &= ENUM_NEGATE(SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
++ sbin->header.flags = 0;
+
+ /* Load block index */
+
+@@ -934,15 +934,16 @@ int sieve_binary_check_executable(struct
+ enum sieve_error *error_code_r,
+ const char **client_error_r)
+ {
++ struct sieve_resource_usage rusage;
++
+ *client_error_r = NULL;
+ sieve_error_args_init(&error_code_r, NULL);
+
+- if (HAS_ALL_BITS(sbin->header.flags,
+- SIEVE_BINARY_FLAG_RESOURCE_LIMIT)) {
++ sieve_binary_get_resource_usage(sbin, &rusage);
++ if (sieve_resource_usage_is_excessive(sbin->svinst, &rusage)) {
+ e_debug(sbin->event,
+ "Binary execution is blocked: "
+- "Cumulative resource usage limit exceeded "
+- "(resource limit flag is set)");
++ "Cumulative resource usage limit exceeded");
+ *error_code_r = SIEVE_ERROR_RESOURCE_LIMIT;
+ *client_error_r = "cumulative resource usage limit exceeded";
+ return 0;
+@@ -959,8 +960,6 @@ int sieve_binary_file_update_resource_us
+ {
+ struct sieve_storage *storage;
+ struct sieve_resource_usage delta;
+- struct sieve_resource_usage total;
+- uint32_t flags;
+
+ sieve_error_args_init(&error_code_r, NULL);
+
+@@ -976,18 +975,19 @@ int sieve_binary_file_update_resource_us
+ storage = sbin->script->storage;
+
+ delta = sbin->rusage;
+- sieve_binary_get_resource_usage(sbin, &total);
+- flags = sbin->header.flags & SIEVE_BINARY_FLAG_RESOURCE_LIMIT;
+
+- if (!HAS_ALL_BITS(flags, SIEVE_BINARY_FLAG_RESOURCE_LIMIT) &&
+- !sieve_resource_usage_is_high(sbin->svinst, &total)) {
+- /* Nothing meaningful to persist */
++ /* Persist only when this run consumed a meaningful amount of CPU.
++ Critically, refused-without-running attempts (delta == 0) must not
++ touch the file - otherwise the file's update_time would be refreshed
++ on every blocked attempt, preventing the resource_usage_timeout from
++ ever decaying the persisted CPU time and re-enabling the user. */
++ if (!sieve_resource_usage_is_high(sbin->svinst, &delta)) {
+ sieve_resource_usage_init(&sbin->rusage);
+ sbin->rusage_updated = FALSE;
+ return 0;
+ }
+
+- if (sieve_rusage_storage_add(storage, &delta, flags) < 0) {
++ if (sieve_rusage_storage_add(storage, &delta) < 0) {
+ *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary-private.h
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary-private.h
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary-private.h
+@@ -13,10 +13,6 @@
+ * Binary file
+ */
+
+-enum SIEVE_BINARY_FLAGS {
+- SIEVE_BINARY_FLAG_RESOURCE_LIMIT = BIT(0),
+-};
+-
+ struct sieve_binary_header {
+ uint32_t magic;
+ uint16_t version_major;
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary.c
+@@ -210,17 +210,14 @@ void sieve_binary_apply_persisted_rusage
+ {
+ struct sieve_storage *storage = sieve_binary_get_user_storage(sbin);
+ struct sieve_resource_usage rusage;
+- struct sieve_binary_header *header = &sbin->header;
+- uint32_t flags = 0;
+
+ if (storage == NULL)
+ return;
+- if (sieve_rusage_storage_load(storage, &rusage, &flags) <= 0)
++ if (sieve_rusage_storage_load(storage, &rusage) <= 0)
+ return;
+
+ sbin->persisted_rusage.cpu_time_msecs = rusage.cpu_time_msecs;
+ sbin->persisted_rusage.update_time = ioloop_time;
+- header->flags |= (flags & SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
+ sieve_resource_usage_init(&sbin->rusage);
+ sbin->rusage_updated = FALSE;
+ }
+@@ -241,16 +238,10 @@ void sieve_binary_get_resource_usage(str
+
+ bool sieve_binary_check_resource_usage(struct sieve_binary *sbin)
+ {
+- struct sieve_binary_header *header = &sbin->header;
+ struct sieve_resource_usage rusage;
+
+ sieve_binary_get_resource_usage(sbin, &rusage);
+-
+- if (sieve_resource_usage_is_excessive(sbin->svinst, &rusage)) {
+- header->flags |= SIEVE_BINARY_FLAG_RESOURCE_LIMIT;
+- return FALSE;
+- }
+- return TRUE;
++ return !sieve_resource_usage_is_excessive(sbin->svinst, &rusage);
+ }
+
+ bool sieve_binary_record_resource_usage(
+Index: trixie/pigeonhole/src/lib-sieve/sieve-rusage.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-rusage.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-rusage.c
+@@ -26,12 +26,11 @@
+ #define SIEVE_RUSAGE_LOCK_TIMEOUT 10
+ #define SIEVE_RUSAGE_LOCK_STALE_TIMEOUT 60
+
+-/* Maximum size of the on-disk file. Single-line ASCII format with four
++/* Maximum size of the on-disk file. Single-line ASCII format with three
+ space-separated tokens; anything larger is treated as corrupt. */
+ #define SIEVE_RUSAGE_MAX_FILE_SIZE 128
+
+ struct sieve_rusage_record {
+- uint32_t flags;
+ uint32_t cpu_time_msecs;
+ time_t update_time;
+ };
+@@ -72,13 +71,11 @@ sieve_rusage_parse(const char *line, str
+ strcmp(tokens[0], SIEVE_RUSAGE_VERSION_TAG) != 0)
+ break;
+ if (tokens[1] == NULL || tokens[2] == NULL ||
+- tokens[3] == NULL || tokens[4] != NULL)
++ tokens[3] != NULL)
+ break;
+- if (str_to_uint32(tokens[1], &rec->flags) < 0)
++ if (str_to_uint32(tokens[1], &cpu_secs) < 0)
+ break;
+- if (str_to_uint32(tokens[2], &cpu_secs) < 0)
+- break;
+- if (str_to_time(tokens[3], &rec->update_time) < 0)
++ if (str_to_time(tokens[2], &rec->update_time) < 0)
+ break;
+ if (cpu_secs > UINT32_MAX / 1000)
+ rec->cpu_time_msecs = UINT32_MAX;
+@@ -148,14 +145,12 @@ sieve_rusage_dotlock_settings(struct dot
+ }
+
+ int sieve_rusage_storage_load(struct sieve_storage *storage,
+- struct sieve_resource_usage *rusage_r,
+- uint32_t *flags_r)
++ struct sieve_resource_usage *rusage_r)
+ {
+ struct sieve_rusage_record rec;
+ unsigned int timeout;
+
+ sieve_resource_usage_init(rusage_r);
+- *flags_r = 0;
+
+ if (storage->rusage_path == NULL)
+ return 0;
+@@ -171,13 +166,11 @@ int sieve_rusage_storage_load(struct sie
+ }
+
+ rusage_r->cpu_time_msecs = rec.cpu_time_msecs;
+- *flags_r = rec.flags;
+ return 1;
+ }
+
+ int sieve_rusage_storage_add(struct sieve_storage *storage,
+- const struct sieve_resource_usage *delta_rusage,
+- uint32_t flags_to_set)
++ const struct sieve_resource_usage *delta_rusage)
+ {
+ struct sieve_rusage_record rec;
+ struct dotlock_settings dlset;
+@@ -226,7 +219,6 @@ int sieve_rusage_storage_add(struct siev
+ (ioloop_time - rec.update_time) > (time_t)timeout) {
+ /* decayed: discard previous values */
+ rec.cpu_time_msecs = 0;
+- rec.flags = 0;
+ }
+
+ old_cpu = rec.cpu_time_msecs;
+@@ -234,7 +226,6 @@ int sieve_rusage_storage_add(struct siev
+ rec.cpu_time_msecs = UINT32_MAX;
+ else
+ rec.cpu_time_msecs = old_cpu + delta_rusage->cpu_time_msecs;
+- rec.flags |= flags_to_set;
+ rec.update_time = ioloop_time;
+
+ /* Round CPU time up to whole seconds for persistence. */
+@@ -244,8 +235,8 @@ int sieve_rusage_storage_add(struct siev
+ cpu_secs = (rec.cpu_time_msecs + 999) / 1000;
+
+ out = t_str_new(64);
+- str_printfa(out, "%s %u %u %lld\n", SIEVE_RUSAGE_VERSION_TAG,
+- rec.flags, cpu_secs, (long long)rec.update_time);
++ str_printfa(out, "%s %u %lld\n", SIEVE_RUSAGE_VERSION_TAG,
++ cpu_secs, (long long)rec.update_time);
+
+ wret = write(fd, str_data(out), str_len(out));
+ if (wret != (ssize_t)str_len(out)) {
+Index: trixie/pigeonhole/src/lib-sieve/sieve-rusage.h
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-rusage.h
++++ trixie/pigeonhole/src/lib-sieve/sieve-rusage.h
+@@ -16,17 +16,13 @@ void sieve_rusage_storage_init(struct si
+
+ /* Load persisted resource usage from the per-user file. Returns 1 on success,
+ 0 if the storage has no rusage file (no INBOX namespace path), -1 on error.
+- Applies the resource_usage_timeout decay. flags_r receives sieve binary
+- header flags (RESOURCE_LIMIT) that were persisted. */
++ Applies the resource_usage_timeout decay. */
+ int sieve_rusage_storage_load(struct sieve_storage *storage,
+- struct sieve_resource_usage *rusage_r,
+- uint32_t *flags_r);
++ struct sieve_resource_usage *rusage_r);
+
+-/* Atomically add delta_rusage to and OR flags_to_set into the persisted
+- per-user rusage file. Read-modify-write under fcntl WRLCK. Returns 1 on
+- success, 0 if no rusage file is configured, -1 on error. */
++/* Atomically add delta_rusage to the persisted per-user rusage file.
++ Returns 1 on success, 0 if no rusage file is configured, -1 on error. */
+ int sieve_rusage_storage_add(struct sieve_storage *storage,
+- const struct sieve_resource_usage *delta_rusage,
+- uint32_t flags_to_set);
++ const struct sieve_resource_usage *delta_rusage);
+
+ #endif
--- /dev/null
+From d7a040f5b0230368dd97112daf7a710446fdfd16 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Thu, 21 May 2026 11:00:56 +0300
+Subject: [PATCH 4/6] lib-var-expand: Change escape func signature to return
+ int with error_r
+
+Wire error propagation in var_expand_program_execute_one_real() so a
+failing escape function causes the expansion to return -1.
+
+Update all implementations (passdb-sql, userdb-sql, db-ldap, dict-ldap,
+auth-request-var-expand) and replace the unsafe auth_request_escape_func_t
+cast in auth-request-var-expand.c with a proper bridge wrapper.
+---
+ src/auth/auth-request-var-expand.c | 13 +++++++++++--
+ src/auth/db-ldap.c | 12 ++++++++----
+ src/auth/db-ldap.h | 4 ++--
+ src/auth/passdb-sql.c | 6 ++++--
+ src/auth/test-auth-request-var-expand.c | 16 ++++++++++++----
+ src/auth/test-ldap.c | 5 +++--
+ src/auth/userdb-sql.c | 6 ++++--
+ src/lib-dict-backend/dict-ldap.c | 6 ++++--
+ src/lib-var-expand/expansion-filter.c | 7 +++++--
+ src/lib-var-expand/expansion-program.c | 11 +++++++----
+ src/lib-var-expand/test-var-expand.c | 6 ++++--
+ src/lib-var-expand/var-expand.h | 7 ++++---
+ 12 files changed, 68 insertions(+), 31 deletions(-)
+
+Index: trixie/src/auth/auth-request-var-expand.c
+===================================================================
+--- trixie.orig/src/auth/auth-request-var-expand.c
++++ trixie/src/auth/auth-request-var-expand.c
+@@ -55,6 +55,15 @@ escape_none(const char *string,
+ return string;
+ }
+
++static int
++auth_request_escape_wrapper(const char *input, const char **output_r,
++ void *context, const char **error_r ATTR_UNUSED)
++{
++ const struct auth_request_var_expand_ctx *ctx = context;
++ *output_r = ctx->escape_func(input, ctx->auth_request);
++ return 0;
++}
++
+ const char *
+ auth_request_str_escape(const char *string,
+ const struct auth_request *request ATTR_UNUSED)
+@@ -242,9 +251,9 @@ int auth_request_var_expand_with_table(s
+ const struct var_expand_params params = {
+ .table = table,
+ .providers = auth_request_var_expand_providers,
+- .escape_func = (var_expand_escape_func_t *)ctx.escape_func,
++ .escape_func = auth_request_escape_wrapper,
+ .context = &ctx,
+- .escape_context = (void *)auth_request,
++ .escape_context = &ctx,
+ .event = auth_request->event,
+ };
+
+Index: trixie/src/auth/db-ldap.c
+===================================================================
+--- trixie.orig/src/auth/db-ldap.c
++++ trixie/src/auth/db-ldap.c
+@@ -1138,7 +1138,8 @@ void db_ldap_get_attribute_names(pool_t
+ *sensitive_r = array_front(&sensitive_attr_names);
+ }
+
+-const char *ldap_escape(const char *input, void *context ATTR_UNUSED)
++int ldap_escape(const char *input, const char **output_r,
++ void *context ATTR_UNUSED, const char **error_r ATTR_UNUSED)
+ {
+ /* This function escapes both LDAP filters and LDAP DNs. This works,
+ because both allow using the method of escaping any characters.
+@@ -1195,8 +1196,10 @@ const char *ldap_escape(const char *inpu
+ (required by DN) */
+ if (pos > 0 && input[pos - 1] == ' ')
+ pos--;
+- else
+- return input;
++ else {
++ *output_r = input;
++ return 0;
++ }
+ }
+ }
+
+@@ -1211,7 +1214,8 @@ const char *ldap_escape(const char *inpu
+ pos--;
+ } while (pos < input_len);
+ str_append_data(str, input, pos);
+- return str_c(str);
++ *output_r = str_c(str);
++ return 0;
+ }
+
+ static bool
+Index: trixie/src/auth/db-ldap.h
+===================================================================
+--- trixie.orig/src/auth/db-ldap.h
++++ trixie/src/auth/db-ldap.h
+@@ -169,8 +169,8 @@ void db_ldap_connect_delayed(struct ldap
+
+ void db_ldap_enable_input(struct ldap_connection *conn, bool enable);
+
+-const char *ldap_dn_escape(const char *str, void *context);
+-const char *ldap_escape(const char *str, void *context);
++int ldap_dn_escape(const char *str, const char **output_r, void *context, const char **error_r);
++int ldap_escape(const char *str, const char **output_r, void *context, const char **error_r);
+ const char *ldap_get_error(struct ldap_connection *conn);
+
+ struct db_ldap_result_iterate_context *
+Index: trixie/src/auth/passdb-sql.c
+===================================================================
+--- trixie.orig/src/auth/passdb-sql.c
++++ trixie/src/auth/passdb-sql.c
+@@ -167,10 +167,12 @@ static void sql_query_callback(struct sq
+ auth_request_unref(&auth_request);
+ }
+
+-static const char *passdb_sql_escape(const char *str, void *context)
++static int passdb_sql_escape(const char *str, const char **output_r,
++ void *context, const char **error_r ATTR_UNUSED)
+ {
+ struct sql_db *db = context;
+- return sql_escape_string(db, str);
++ *output_r = sql_escape_string(db, str);
++ return 0;
+ }
+
+ static void sql_lookup_pass(struct passdb_sql_request *sql_request)
+Index: trixie/src/auth/test-auth-request-var-expand.c
+===================================================================
+--- trixie.orig/src/auth/test-auth-request-var-expand.c
++++ trixie/src/auth/test-auth-request-var-expand.c
+@@ -70,6 +70,14 @@ test_escape(const char *string, const st
+ return dest;
+ }
+
++static int
++test_escape_varexpand(const char *string, const char **output_r,
++ void *context, const char **error_r ATTR_UNUSED)
++{
++ *output_r = test_escape(string, context);
++ return 0;
++}
++
+ static bool test_empty_request(string_t *str, const char *input)
+ {
+ const struct var_expand_params params = {
+@@ -102,7 +110,7 @@ static void test_auth_request_var_expand
+
+ const struct var_expand_params params = {
+ .table = auth_request_get_var_expand_table(&test_request),
+- .escape_func = (var_expand_escape_func_t *)test_escape,
++ .escape_func = test_escape_varexpand,
+ .escape_context = &test_request,
+ };
+
+@@ -129,7 +137,7 @@ static void test_auth_request_var_expand
+
+ struct var_expand_params params = {
+ .table = auth_request_get_var_expand_table(&test_request),
+- .escape_func = (var_expand_escape_func_t *)test_escape,
++ .escape_func = test_escape_varexpand,
+ .escape_context = &test_request
+ };
+ test_assert(var_expand(str, test_input, ¶ms, &error) == 0);
+@@ -169,7 +177,7 @@ static void test_auth_request_var_expand
+
+ const struct var_expand_params params = {
+ .table = auth_request_get_var_expand_table(&test_request),
+- .escape_func = (var_expand_escape_func_t *)test_escape,
++ .escape_func = test_escape_varexpand,
+ .escape_context = &test_request,
+ };
+
+@@ -200,7 +208,7 @@ static void test_auth_request_var_expand
+ test_request.fields.user = t_strdup_noconst(tests[i].username);
+ const struct var_expand_params params = {
+ .table = auth_request_get_var_expand_table(&test_request),
+- .escape_func = (var_expand_escape_func_t *)test_escape,
++ .escape_func = test_escape_varexpand,
+ .escape_context = &test_request,
+ };
+ str_truncate(str, 0);
+Index: trixie/src/auth/test-ldap.c
+===================================================================
+--- trixie.orig/src/auth/test-ldap.c
++++ trixie/src/auth/test-ldap.c
+@@ -25,8 +25,9 @@ static void test_ldap_escape(void)
+ };
+ test_begin("ldap_escape()");
+ for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
+- test_assert_strcmp_idx(ldap_escape(tests[i].input, NULL),
+- tests[i].output, i);
++ const char *output, *error;
++ test_assert(ldap_escape(tests[i].input, &output, NULL, &error) == 0);
++ test_assert_strcmp_idx(output, tests[i].output, i);
+ }
+ test_end();
+ }
+Index: trixie/src/auth/userdb-sql.c
+===================================================================
+--- trixie.orig/src/auth/userdb-sql.c
++++ trixie/src/auth/userdb-sql.c
+@@ -112,10 +112,12 @@ static void sql_query_callback(struct sq
+ i_free(sql_request);
+ }
+
+-static const char *userdb_sql_escape(const char *str, void *context)
++static int userdb_sql_escape(const char *str, const char **output_r,
++ void *context, const char **error_r ATTR_UNUSED)
+ {
+ struct sql_db *db = context;
+- return sql_escape_string(db, str);
++ *output_r = sql_escape_string(db, str);
++ return 0;
+ }
+
+ static void userdb_sql_lookup(struct auth_request *auth_request,
+Index: trixie/src/lib-dict-backend/dict-ldap.c
+===================================================================
+--- trixie.orig/src/lib-dict-backend/dict-ldap.c
++++ trixie/src/lib-dict-backend/dict-ldap.c
+@@ -139,7 +139,8 @@ int dict_ldap_connect(struct ldap_dict *
+ #define IS_LDAP_ESCAPED_CHAR(c) \
+ ((((unsigned char)(c)) & 0x80) != 0 || strchr(LDAP_ESCAPE_CHARS, (c)) != NULL)
+
+-static const char *ldap_escape(const char *str, void *context ATTR_UNUSED)
++static int ldap_escape(const char *str, const char **output_r,
++ void *context ATTR_UNUSED, const char **error_r ATTR_UNUSED)
+ {
+ string_t *ret = NULL;
+
+@@ -154,7 +155,8 @@ static const char *ldap_escape(const cha
+ str_append_c(ret, *p);
+ }
+
+- return ret == NULL ? str : str_c(ret);
++ *output_r = ret == NULL ? str : str_c(ret);
++ return 0;
+ }
+
+ static
+Index: trixie/src/lib-var-expand/expansion-program.c
+===================================================================
+--- trixie.orig/src/lib-var-expand/expansion-program.c
++++ trixie/src/lib-var-expand/expansion-program.c
+@@ -142,9 +142,11 @@ int var_expand_program_execute(string_t
+ if (state.transfer_set) {
+ if (!program->only_literal && !state.transfer_safe &&
+ params->escape_func != NULL) {
+- str_append(state.result,
+- params->escape_func(str_c(state.transfer),
+- params->escape_context));
++ const char *escaped;
++ if (params->escape_func(str_c(state.transfer), &escaped,
++ params->escape_context, error_r) < 0)
++ return -1;
++ str_append(state.result, escaped);
+ } else
+ str_append_str(state.result, state.transfer);
+ } else {
+Index: trixie/src/lib-var-expand/test-var-expand.c
+===================================================================
+--- trixie.orig/src/lib-var-expand/test-var-expand.c
++++ trixie/src/lib-var-expand/test-var-expand.c
+@@ -586,7 +586,8 @@ static void test_var_expand_tables_arr(v
+ test_end();
+ }
+
+-static const char *test_escape(const char *str, void *context)
++static int test_escape(const char *str, const char **output_r,
++ void *context, const char **error_r ATTR_UNUSED)
+ {
+ const char *escape_chars = context;
+ string_t *dest = t_str_new(strlen(str) + 2);
+@@ -601,7 +602,8 @@ static const char *test_escape(const cha
+ }
+ }
+ str_append_c(dest, '\'');
+- return str_c(dest);
++ *output_r = str_c(dest);
++ return 0;
+ }
+
+ static void test_var_expand_escape(void)
+Index: trixie/src/lib-var-expand/var-expand.h
+===================================================================
+--- trixie.orig/src/lib-var-expand/var-expand.h
++++ trixie/src/lib-var-expand/var-expand.h
+@@ -11,9 +11,10 @@
+ */
+ typedef int value_provider_func_t(const char *key, const char **value_r,
+ void *context, const char **error_r);
+-/* Used for escaping values, gets given string to escape and context,
+- must return escaped string. */
+-typedef const char *var_expand_escape_func_t(const char *str, void *context);
++/* Used for escaping values. On success sets output_r and returns 0.
++ On failure sets error_r and returns -1. */
++typedef int var_expand_escape_func_t(const char *input, const char **output_r,
++ void *context, const char **error_r);
+
+ struct var_expand_parser_state;
+ struct var_expand_program;
--- /dev/null
+From 4e968e6c8614a935a83024ba4ac9a5c9b6c6b7ff Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 7 May 2026 10:58:12 +0000
+Subject: [PATCH 4/5] submission-login: client-authenticate - Reply 421 4.7.0
+ on mail_max_userip_connections
+
+Until now the connection limit was reported via the same 454 4.7.0 reply
+that is used for generic temporary authentication failures. That makes
+proxies (including Dovecot's own submission proxy) treat the rejection as
+a transient auth error and retry, which is futile when the limit is hit
+and only obscures the actual cause in the proxy log.
+
+Reply with 421 4.7.0 instead. RFC 5321 Section 4.2.1 specifies 421 as
+"service shutting down, closing transmission channel", which is the
+right signal for "do not retry on this connection". The 421 + 4.7.0
+combination is unique among the 421 replies emitted by submission and is
+used by the submission proxy to recognize this specifically as a
+connection-limit reply rather than a generic 421 internal/shutdown.
+---
+ src/submission-login/client-authenticate.c | 19 ++++++++++++++++++-
+ 1 file changed, 18 insertions(+), 1 deletion(-)
+
+Index: trixie/src/submission-login/client-authenticate.c
+===================================================================
+--- trixie.orig/src/submission-login/client-authenticate.c
++++ trixie/src/submission-login/client-authenticate.c
+@@ -151,6 +151,24 @@ void submission_client_auth_result(struc
+ */
+ smtp_server_reply(cmd, 454, "4.7.0", "%s", text);
+ break;
++ case CLIENT_AUTH_RESULT_LIMIT_REACHED:
++ /* The user has too many concurrent connections. Reply with
++ 421 4.7.0: 421 means "service shutting down, closing
++ transmission channel" (RFC 5321 Section 4.2.1) and
++ signals the client that retrying on this same connection
++ is pointless. The proxy uses the 421 + 4.7.0 combination
++ to recognize this specifically as a connection-limit
++ response (rather than a generic 421 internal/shutdown
++ reply) and avoid reconnecting.
++
++ Use reply_immediate() rather than the queued
++ smtp_server_reply() because the caller (sasl-server)
++ immediately tears the connection down after this returns,
++ which would abort a queued reply before it reaches the
++ wire. */
++ smtp_server_connection_reply_immediate(subm_client->conn,
++ 421, "4.7.0 %s", text);
++ break;
+ case CLIENT_AUTH_RESULT_ABORTED:
+ /* RFC4954, Section 4:
+
--- /dev/null
+From c27391fb8528e95e2b2b30c9694aab1ab4341b14 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 4 Jun 2025 17:05:36 +0300
+Subject: [PATCH 5/6] imap: Add imap_compress_on_proxy hidden setting
+
+Keep it disabled by default for now. Once we're sure COMPRESS on proxy
+works properly we'll enable it again.
+---
+ src/imap/cmd-compress.c | 3 ++-
+ src/imap/imap-settings.c | 2 ++
+ src/imap/imap-settings.h | 1 +
+ 3 files changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/src/imap/cmd-compress.c b/src/imap/cmd-compress.c
+index 18aa988892..0367cc6f64 100644
+--- a/src/imap/cmd-compress.c
++++ b/src/imap/cmd-compress.c
+@@ -87,7 +87,8 @@ bool cmd_compress(struct client_command_context *cmd)
+ return TRUE;
+ }
+
+- if (client->multiplex_output != NULL) {
++ if (client->multiplex_output != NULL &&
++ client->set->imap_compress_on_proxy) {
+ /* Let imap-login process handle the COMPRESS. It's the one
+ that will send the tagged reply to the client. */
+ client->compress_handler = handler;
+diff --git a/src/imap/imap-settings.c b/src/imap/imap-settings.c
+index aa0cd747cb..3e2561ccd2 100644
+--- a/src/imap/imap-settings.c
++++ b/src/imap/imap-settings.c
+@@ -81,6 +81,7 @@ static const struct setting_define imap_setting_defines[] = {
+ DEF(ENUM, imap_fetch_failure),
+ DEF(BOOL, imap_metadata),
+ DEF(BOOL, imap_literal_minus),
++ DEF(BOOL_HIDDEN, imap_compress_on_proxy),
+ DEF(BOOL, mail_utf8_extensions),
+ #ifdef BUILD_IMAP_HIBERNATE
+ DEF(TIME, imap_hibernate_timeout),
+@@ -115,6 +116,7 @@ static const struct imap_settings imap_default_settings = {
+ .imap_fetch_failure = "disconnect-immediately:disconnect-after:no-after",
+ .imap_metadata = FALSE,
+ .imap_literal_minus = FALSE,
++ .imap_compress_on_proxy = FALSE,
+ .mail_utf8_extensions = FALSE,
+ #ifdef DOVECOT_PRO_EDITION
+ .imap_hibernate_timeout = 30,
+diff --git a/src/imap/imap-settings.h b/src/imap/imap-settings.h
+index 202ea45a30..347d85cec7 100644
+--- a/src/imap/imap-settings.h
++++ b/src/imap/imap-settings.h
+@@ -34,6 +34,7 @@ struct imap_settings {
+ const char *imap_fetch_failure;
+ bool imap_metadata;
+ bool imap_literal_minus;
++ bool imap_compress_on_proxy;
+ bool mail_utf8_extensions;
+ unsigned int imap_hibernate_timeout;
+ ARRAY_TYPE(const_string) imap_id_send;
+--
+2.47.3
+
--- /dev/null
+From 626a19e307e915b2c545cad226aa57aec8620906 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 3 Jun 2026 21:14:51 +0000
+Subject: [PATCH 5/5] lib: Add comments about memory allocations and string
+ functions preserving errno
+
+---
+ src/lib/data-stack.h | 5 +++++
+ src/lib/imem.h | 3 +++
+ src/lib/mempool.h | 6 ++++++
+ src/lib/strfuncs.h | 7 +++++++
+ 4 files changed, 21 insertions(+)
+
+diff --git a/src/lib/data-stack.h b/src/lib/data-stack.h
+index 7ff66c4ef4..e0febe494f 100644
+--- a/src/lib/data-stack.h
++++ b/src/lib/data-stack.h
+@@ -31,6 +31,11 @@
+ overflows.
+ */
+
++/* All data stack allocations - t_malloc(), t_malloc0(), t_buffer_get(),
++ t_try_realloc() etc. - preserve errno. It is therefore safe to allocate
++ temporary memory between a failing syscall and reading errno (or
++ formatting "%m"). */
++
+ #ifndef STATIC_CHECKER
+ typedef unsigned int data_stack_frame_t;
+ #else
+diff --git a/src/lib/imem.h b/src/lib/imem.h
+index ed8c2eb581..6635bc018a 100644
+--- a/src/lib/imem.h
++++ b/src/lib/imem.h
+@@ -3,6 +3,9 @@
+
+ /* For easy allocation of memory from default memory pool. */
+
++/* Like all pool allocations, i_malloc()/i_realloc()/i_free() and the
++ i_strdup*() helpers preserve errno (see mempool.h). */
++
+ extern pool_t default_pool;
+
+ #define i_new(type, count) p_new(default_pool, type, count)
+diff --git a/src/lib/mempool.h b/src/lib/mempool.h
+index 3b9872cb20..37ecff4c7c 100644
+--- a/src/lib/mempool.h
++++ b/src/lib/mempool.h
+@@ -19,6 +19,12 @@
+ zeroed, it will cost only a few CPU cycles and may well save some debug
+ time. */
+
++/* All pool memory operations - p_malloc(), p_realloc(), p_free() and the
++ p_new()/p_strdup*() helpers built on them - preserve errno. This means an
++ allocation between a failing syscall and reading errno (e.g. when building
++ an error string with "%m") will not clobber errno. The same guarantee holds
++ for the t_* (data stack) and i_* (default pool) allocators. */
++
+ typedef struct pool *pool_t;
+
+ struct pool_vfuncs {
+diff --git a/src/lib/strfuncs.h b/src/lib/strfuncs.h
+index 715af58d8e..e6daa14847 100644
+--- a/src/lib/strfuncs.h
++++ b/src/lib/strfuncs.h
+@@ -13,6 +13,13 @@ extern const char *const empty_str_array[];
+ int i_snprintf(char *dest, size_t max_chars, const char *format, ...)
+ ATTR_FORMAT(3, 4);
+
++/* The p_/t_/i_ strdup, strconcat and *printf helpers below preserve errno.
++ So this pattern is safe - errno still refers to the failed syscall when
++ "%m" is expanded:
++
++ if (syscall(...) < 0)
++ error = t_strdup_printf("syscall() failed: %m"); */
++
+ char *p_strdup(pool_t pool, const char *str) ATTR_MALLOC;
+ void *p_memdup(pool_t pool, const void *data, size_t size) ATTR_MALLOC;
+ /* return NULL if str = "" */
+--
+2.39.5
+
--- /dev/null
+From b0f98d919ca586171c8149e52326e6f0c0ec5252 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Mon, 17 Aug 2026 11:13:18 +0300
+Subject: [PATCH 05/12] lib-index: mail-index-strmap - Fix OOB read from
+ truncated record size
+
+---
+ src/lib-index/mail-index-strmap.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/lib-index/mail-index-strmap.c b/src/lib-index/mail-index-strmap.c
+index fb92517068..9ce2de9103 100644
+--- a/src/lib-index/mail-index-strmap.c
++++ b/src/lib-index/mail-index-strmap.c
+@@ -425,6 +425,9 @@ mail_index_strmap_read_rec_first(struct mail_index_strmap_read_context *ctx,
+ if (mail_index_strmap_read_packed(ctx, &n) <= 0)
+ return -1;
+ count = n < 2 ? n + 1 : n;
++ /* check that rec_size fits */
++ if (UINT_MAX / (sizeof(ctx->rec.str_idx) + sizeof(*crc32_r)) < count)
++ return -1;
+ ctx->view->total_ref_count += count;
+
+ ctx->rec_size = count * (sizeof(ctx->rec.str_idx) + sizeof(*crc32_r));
+--
+2.47.3
+
--- /dev/null
+From ef2cddece8101876c0e756536e6ef358c780dc07 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 17 Apr 2026 15:46:10 +0200
+Subject: [PATCH 05/14] lib-mail: Add count field to struct
+ message_address_list
+
+Add a count field and increment it in add_address() so callers can read
+the number of parsed addresses directly from the list struct without
+walking the linked list.
+
+Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
+---
+ src/lib-mail/message-address.c | 1 +
+ src/lib-mail/message-address.h | 1 +
+ src/lib-mail/message-part-data.c | 1 +
+ 3 files changed, 3 insertions(+)
+
+diff --git a/src/lib-mail/message-address.c b/src/lib-mail/message-address.c
+index 675e652e04..967cc518bf 100644
+--- a/src/lib-mail/message-address.c
++++ b/src/lib-mail/message-address.c
+@@ -30,6 +30,7 @@ static void add_address(struct message_address_parser_context *ctx)
+ i_zero(&ctx->addr);
+
+ DLLIST2_APPEND(&ctx->addr_list.head, &ctx->addr_list.tail, addr);
++ ctx->addr_list.count++;
+ }
+
+ /* quote with "" and escape all '\', '"' and "'" characters if need */
+diff --git a/src/lib-mail/message-address.h b/src/lib-mail/message-address.h
+index 224f7a7560..8b99ce1401 100644
+--- a/src/lib-mail/message-address.h
++++ b/src/lib-mail/message-address.h
+@@ -33,6 +33,7 @@ struct message_address {
+
+ struct message_address_list {
+ struct message_address *head, *tail;
++ unsigned int count;
+ };
+
+ /* Parse message addresses from given data. Note that giving an empty string
+diff --git a/src/lib-mail/message-part-data.c b/src/lib-mail/message-part-data.c
+index 895bf88e85..acd50e5210 100644
+--- a/src/lib-mail/message-part-data.c
++++ b/src/lib-mail/message-part-data.c
+@@ -247,6 +247,7 @@ void message_part_envelope_parse_from_header(pool_t pool,
+ validated while MUA only shows the second From header. */
+ DLLIST2_JOIN(&addr_p->head, &addr_p->tail,
+ &new_addr.head, &new_addr.tail);
++ addr_p->count += new_addr.count;
+ } else if (str_p != NULL) {
+ *str_p = message_header_strdup(pool, hdr->full_value,
+ hdr->full_value_len);
+--
+2.47.3
+
--- /dev/null
+From 88470b80fb4079cd2207bc8c4e986b816218421e Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 5 May 2026 16:51:27 +0000
+Subject: [PATCH 5/7] lib-sieve: file storage: Remove orphan compiled binaries
+ on delete and rename
+
+DELETESCRIPT and RENAMESCRIPT only operated on the .sieve file and
+left the matching compiled binary (.svbin) on disk. The orphan was
+never referenced again under that name, so it accumulated as wasted
+disk space until something else (manual cleanup, account removal)
+removed it.
+
+Unlink fscript->bin_path alongside the .sieve operation. The unlink is
+best-effort: failures other than ENOENT are logged but do not fail the
+DELETE/RENAME.
+---
+ src/lib-sieve/storage/file/sieve-file-script.c | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+Index: trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
++++ trixie/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
+@@ -659,6 +659,14 @@ static int sieve_file_storage_script_del
+ fscript->path);
+ }
+ }
++
++ /* Best-effort: drop the compiled binary as well so it does not
++ linger as a stale orphan. */
++ if (fscript->bin_path != NULL &&
++ unlink(fscript->bin_path) < 0 && errno != ENOENT) {
++ e_error(script->event,
++ "unlink(%s) failed: %m", fscript->bin_path);
++ }
+ return ret;
+ }
+
+@@ -791,6 +799,16 @@ sieve_file_storage_script_rename(struct
+ fscript->path);
+ }
+
++ /* Drop the old compiled binary; the renamed
++ script will recompile under its new name. */
++ if (fscript->bin_path != NULL &&
++ unlink(fscript->bin_path) < 0 &&
++ errno != ENOENT) {
++ e_error(script->event,
++ "unlink(%s) failed: %m",
++ fscript->bin_path);
++ }
++
+ if (script->name != NULL && *script->name != '\0')
+ script->name = p_strdup(script->pool, newname);
+ fscript->path = p_strdup(script->pool, newpath);
--- /dev/null
+From 4e83f5367d2b897430c91c8be9f0830b16e3d90f Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Fri, 29 May 2026 11:22:57 +0000
+Subject: [PATCH 5/6] lib-sql: Make escape_string return int with error_r, fail
+ instead of unsafe fallback
+
+Change escape_string driver vfunc and sql_escape_string() to return int
+with separate error_r output parameter. On failure (e.g. not connected),
+return -1 instead of falling back to unsafe escaping.
+
+Move escaping from sql_statement_bind_str() to sql_statement_get_query()
+so errors can be propagated to callers. Add failed_error field to
+sql_transaction_context for deferred error reporting at commit time.
+---
+ src/auth/passdb-sql.c | 5 +-
+ src/auth/userdb-sql.c | 5 +-
+ src/lib-sql/driver-cassandra.c | 52 ++++++++++++-----
+ src/lib-sql/driver-mysql.c | 20 +++----
+ src/lib-sql/driver-pgsql.c | 27 ++++++---
+ src/lib-sql/driver-sqlite.c | 8 ++-
+ src/lib-sql/driver-sqlpool.c | 10 ++--
+ src/lib-sql/driver-test.c | 27 +++++----
+ src/lib-sql/sql-api-private.h | 8 ++-
+ src/lib-sql/sql-api.c | 103 +++++++++++++++++++++++++++------
+ src/lib-sql/sql-api.h | 3 +-
+ 11 files changed, 188 insertions(+), 80 deletions(-)
+
+Index: trixie/src/auth/passdb-sql.c
+===================================================================
+--- trixie.orig/src/auth/passdb-sql.c
++++ trixie/src/auth/passdb-sql.c
+@@ -168,11 +168,10 @@ static void sql_query_callback(struct sq
+ }
+
+ static int passdb_sql_escape(const char *str, const char **output_r,
+- void *context, const char **error_r ATTR_UNUSED)
++ void *context, const char **error_r)
+ {
+ struct sql_db *db = context;
+- *output_r = sql_escape_string(db, str);
+- return 0;
++ return sql_escape_string(db, str, output_r, error_r);
+ }
+
+ static void sql_lookup_pass(struct passdb_sql_request *sql_request)
+Index: trixie/src/auth/userdb-sql.c
+===================================================================
+--- trixie.orig/src/auth/userdb-sql.c
++++ trixie/src/auth/userdb-sql.c
+@@ -113,11 +113,10 @@ static void sql_query_callback(struct sq
+ }
+
+ static int userdb_sql_escape(const char *str, const char **output_r,
+- void *context, const char **error_r ATTR_UNUSED)
++ void *context, const char **error_r)
+ {
+ struct sql_db *db = context;
+- *output_r = sql_escape_string(db, str);
+- return 0;
++ return sql_escape_string(db, str, output_r, error_r);
+ }
+
+ static void userdb_sql_lookup(struct auth_request *auth_request,
+Index: trixie/src/lib-sql/driver-cassandra.c
+===================================================================
+--- trixie.orig/src/lib-sql/driver-cassandra.c
++++ trixie/src/lib-sql/driver-cassandra.c
+@@ -830,22 +830,26 @@ static void driver_cassandra_disconnect(
+ driver_cassandra_close(db, "Disconnected");
+ }
+
+-static const char *
++static int
+ driver_cassandra_escape_string(struct sql_db *db ATTR_UNUSED,
+- const char *string)
++ const char *string, const char **output_r,
++ const char **error_r ATTR_UNUSED)
+ {
+ string_t *escaped;
+ unsigned int i;
+
+- if (strchr(string, '\'') == NULL)
+- return string;
++ if (strchr(string, '\'') == NULL) {
++ *output_r = string;
++ return 0;
++ }
+ escaped = t_str_new(strlen(string)+10);
+ for (i = 0; string[i] != '\0'; i++) {
+ if (string[i] == '\'')
+ str_append_c(escaped, '\'');
+ str_append_c(escaped, string[i]);
+ }
+- return str_c(escaped);
++ *output_r = str_c(escaped);
++ return 0;
+ }
+
+ static void
+@@ -2192,8 +2196,13 @@ static void cassandra_transaction_finish
+ if (stmt->prep == NULL)
+ have_nonprepared = TRUE;
+ if (stmt->cass_stmt == NULL) {
+- stmt->cass_stmt = cass_statement_new(
+- sql_statement_get_query(&stmt->stmt), 0);
++ const char *query, *error;
++ if (sql_statement_get_query(&stmt->stmt,
++ &query, &error) < 0) {
++ cassandra_transaction_finish(ctx, error);
++ return;
++ }
++ stmt->cass_stmt = cass_statement_new(query, 0);
+ if (stmt->timestamp != 0) {
+ cass_statement_set_timestamp(stmt->cass_stmt,
+ stmt->timestamp);
+@@ -2289,11 +2298,15 @@ driver_cassandra_try_commit_s(struct cas
+ i_panic("cassandra: sql_transaction_commit_s() not supported for prepared statements");
+ }
+
++ const char *query, *error;
++ if (sql_statement_get_query(&stmt->stmt, &query, &error) < 0) {
++ transaction_set_failed(ctx, error);
++ return;
++ }
++
+ /* just a single query, send it */
+ driver_cassandra_sync_init(db);
+- result = driver_cassandra_sync_query(db,
+- sql_statement_get_query(&stmt->stmt),
+- ctx->query_type);
++ result = driver_cassandra_sync_query(db, query, ctx->query_type);
+ driver_cassandra_sync_deinit(db);
+
+ if (sql_result_next_row(result) < 0)
+@@ -2796,8 +2809,14 @@ driver_cassandra_statement_query(struct
+ } else {
+ /* Not a prepared statement. Generate a statement from
+ the query string. */
+- stmt->result->statement =
+- cass_statement_new(sql_statement_get_query(_stmt), 0);
++ const char *query, *error;
++ if (sql_statement_get_query(_stmt, &query, &error) < 0) {
++ stmt->result->error = i_strdup(error);
++ result_finish(stmt->result);
++ cassandra_sql_statement_free(stmt);
++ return;
++ }
++ stmt->result->statement = cass_statement_new(query, 0);
+ stmt->result->timestamp = stmt->timestamp;
+ if (stmt->timestamp != 0) {
+ cass_statement_set_timestamp(stmt->result->statement,
+@@ -2826,8 +2845,13 @@ driver_cassandra_update_stmt(struct sql_
+
+ i_assert(affected_rows == NULL);
+
+- if (!driver_cassandra_update_query_type(ctx,
+- sql_statement_get_query(_stmt))) {
++ const char *query, *error;
++ if (sql_statement_get_query(_stmt, &query, &error) < 0) {
++ transaction_set_failed(ctx, error);
++ cassandra_sql_statement_free(stmt);
++ return;
++ }
++ if (!driver_cassandra_update_query_type(ctx, query)) {
+ cassandra_sql_statement_free(stmt);
+ return;
+ }
+Index: trixie/src/lib-sql/driver-mysql.c
+===================================================================
+--- trixie.orig/src/lib-sql/driver-mysql.c
++++ trixie/src/lib-sql/driver-mysql.c
+@@ -462,8 +462,9 @@ static int driver_mysql_do_query(struct
+ return -1;
+ }
+
+-static const char *
+-driver_mysql_escape_string(struct sql_db *_db, const char *string)
++static int
++driver_mysql_escape_string(struct sql_db *_db, const char *string,
++ const char **output_r, const char **error_r)
+ {
+ struct mysql_db *db = container_of(_db, struct mysql_db, api);
+ size_t len = strlen(string);
+@@ -475,22 +476,15 @@ driver_mysql_escape_string(struct sql_db
+ }
+
+ if (_db->state == SQL_DB_STATE_DISCONNECTED) {
+- /* FIXME: we don't have a valid connection, so fallback
+- to using default escaping. the next query will most
+- likely fail anyway so it shouldn't matter that much
+- what we return here.. Anyway, this API needs
+- changing so that the escaping function could already
+- fail the query reliably. */
+- to = t_buffer_get(len * 2 + 1);
+- len = mysql_escape_string(to, string, len);
+- t_buffer_alloc(len + 1);
+- return to;
++ *error_r = SQL_ERRSTR_NOT_CONNECTED;
++ return -1;
+ }
+
+ to = t_buffer_get(len * 2 + 1);
+ len = mysql_real_escape_string(db->mysql, to, string, len);
+ t_buffer_alloc(len + 1);
+- return to;
++ *output_r = to;
++ return 0;
+ }
+
+ static void driver_mysql_exec(struct sql_db *_db, const char *query)
+Index: trixie/src/lib-sql/driver-pgsql.c
+===================================================================
+--- trixie.orig/src/lib-sql/driver-pgsql.c
++++ trixie/src/lib-sql/driver-pgsql.c
+@@ -718,8 +718,9 @@ static void do_query(struct pgsql_result
+ }
+ }
+
+-static const char *
+-driver_pgsql_escape_string(struct sql_db *_db, const char *string)
++static int
++driver_pgsql_escape_string(struct sql_db *_db, const char *string,
++ const char **output_r, const char **error_r)
+ {
+ struct pgsql_db *db = (struct pgsql_db *)_db;
+ size_t len = strlen(string);
+@@ -735,14 +736,24 @@ driver_pgsql_escape_string(struct sql_db
+
+ to = t_buffer_get(len * 2 + 1);
+ len = PQescapeStringConn(db->pg, to, string, len, &error);
+- } else
+-#endif
+- {
+- to = t_buffer_get(len * 2 + 1);
+- len = PQescapeString(to, string, len);
++ if (error != 0) {
++ *error_r = last_error(db);
++ return -1;
++ }
++ t_buffer_alloc(len + 1);
++ *output_r = to;
++ return 0;
++ } else {
++ *error_r = SQL_ERRSTR_NOT_CONNECTED;
++ return -1;
+ }
++#else
++ to = t_buffer_get(len * 2 + 1);
++ len = PQescapeString(to, string, len);
+ t_buffer_alloc(len + 1);
+- return to;
++ *output_r = to;
++ return 0;
++#endif
+ }
+
+ static void exec_callback(struct sql_result *_result,
+Index: trixie/src/lib-sql/driver-sqlite.c
+===================================================================
+--- trixie.orig/src/lib-sql/driver-sqlite.c
++++ trixie/src/lib-sql/driver-sqlite.c
+@@ -221,15 +221,17 @@ static void driver_sqlite_deinit_v(struc
+ i_free(db);
+ }
+
+-static const char *
++static int
+ driver_sqlite_escape_string(struct sql_db *_db ATTR_UNUSED,
+- const char *string)
++ const char *string, const char **output_r,
++ const char **error_r ATTR_UNUSED)
+ {
+ const size_t len = strlen(string) * 2 + 1;
+ char *escaped = t_malloc_no0(len);
+ if (sqlite3_snprintf(len, escaped, "%q", string) == NULL)
+ i_unreached();
+- return escaped;
++ *output_r = escaped;
++ return 0;
+ }
+
+ static const char *
+Index: trixie/src/lib-sql/driver-sqlpool.c
+===================================================================
+--- trixie.orig/src/lib-sql/driver-sqlpool.c
++++ trixie/src/lib-sql/driver-sqlpool.c
+@@ -573,8 +573,9 @@ static void driver_sqlpool_disconnect(st
+ driver_sqlpool_abort_requests(db);
+ }
+
+-static const char *
+-driver_sqlpool_escape_string(struct sql_db *_db, const char *string)
++static int
++driver_sqlpool_escape_string(struct sql_db *_db, const char *string,
++ const char **output_r, const char **error_r)
+ {
+ struct sqlpool_db *db = (struct sqlpool_db *)_db;
+ const struct sqlpool_connection *conns;
+@@ -584,11 +585,12 @@ driver_sqlpool_escape_string(struct sql_
+ conns = array_get(&db->all_connections, &count);
+ for (i = 0; i < count; i++) {
+ if (SQL_DB_IS_READY(conns[i].db))
+- return sql_escape_string(conns[i].db, string);
++ return sql_escape_string(conns[i].db, string,
++ output_r, error_r);
+ }
+ /* no ready connections. just use the first one (we're guaranteed
+ to always have one) */
+- return sql_escape_string(conns[0].db, string);
++ return sql_escape_string(conns[0].db, string, output_r, error_r);
+ }
+
+ static void driver_sqlpool_timeout(struct sqlpool_db *db)
+Index: trixie/src/lib-sql/driver-test.c
+===================================================================
+--- trixie.orig/src/lib-sql/driver-test.c
++++ trixie/src/lib-sql/driver-test.c
+@@ -33,10 +33,12 @@ static int driver_test_sqlite_init(struc
+ static void driver_test_deinit(struct sql_db *_db);
+ static int driver_test_connect(struct sql_db *_db);
+ static void driver_test_disconnect(struct sql_db *_db);
+-static const char *
+-driver_test_mysql_escape_string(struct sql_db *_db, const char *string);
+-static const char *
+-driver_test_escape_string(struct sql_db *_db, const char *string);
++static int
++driver_test_mysql_escape_string(struct sql_db *_db, const char *string,
++ const char **output_r, const char **error_r);
++static int
++driver_test_escape_string(struct sql_db *_db, const char *string,
++ const char **output_r, const char **error_r);
+ static void driver_test_exec(struct sql_db *_db, const char *query);
+ static void driver_test_query(struct sql_db *_db, const char *query,
+ sql_query_callback_t *callback, void *context);
+@@ -237,9 +239,10 @@ static int driver_test_connect(struct sq
+ static void driver_test_disconnect(struct sql_db *_db ATTR_UNUSED)
+ { }
+
+-static const char *
++static int
+ driver_test_mysql_escape_string(struct sql_db *_db ATTR_UNUSED,
+- const char *string)
++ const char *string, const char **output_r,
++ const char **error_r ATTR_UNUSED)
+ {
+ string_t *esc = t_str_new(strlen(string));
+ for(const char *ptr = string; *ptr != '\0'; ptr++) {
+@@ -248,13 +251,17 @@ driver_test_mysql_escape_string(struct s
+ str_append_c(esc, '\\');
+ str_append_c(esc, *ptr);
+ }
+- return str_c(esc);
++ *output_r = str_c(esc);
++ return 0;
+ }
+
+-static const char *
+-driver_test_escape_string(struct sql_db *_db ATTR_UNUSED, const char *string)
++static int
++driver_test_escape_string(struct sql_db *_db ATTR_UNUSED, const char *string,
++ const char **output_r,
++ const char **error_r ATTR_UNUSED)
+ {
+- return string;
++ *output_r = string;
++ return 0;
+ }
+
+ static void driver_test_exec(struct sql_db *_db, const char *query)
+Index: trixie/src/lib-sql/sql-api-private.h
+===================================================================
+--- trixie.orig/src/lib-sql/sql-api-private.h
++++ trixie/src/lib-sql/sql-api-private.h
+@@ -81,7 +81,8 @@ struct sql_db_vfuncs {
+
+ int (*connect)(struct sql_db *db);
+ void (*disconnect)(struct sql_db *db);
+- const char *(*escape_string)(struct sql_db *db, const char *string);
++ int (*escape_string)(struct sql_db *db, const char *string,
++ const char **output_r, const char **error_r);
+
+ void (*exec)(struct sql_db *db, const char *query);
+ /* Only implement this if the driver can really do asynchronous callbacks,
+@@ -215,6 +216,7 @@ struct sql_statement {
+ pool_t pool;
+ const char *query_template;
+ ARRAY_TYPE(const_string) args;
++ ARRAY(bool) args_need_escaping;
+
+ /* Tell the driver to not log this query with expanded values.
+ This works only for prepared statements. */
+@@ -252,6 +254,7 @@ struct sql_transaction_context {
+ /* commit() must use this query list if head is non-NULL. */
+ struct sql_transaction_query *head, *tail;
+
++ char *failed_error;
+ bool non_atomic;
+ };
+
+@@ -277,7 +280,8 @@ inline static const char *sql_db_table_p
+ void sql_transaction_add_query(struct sql_transaction_context *ctx, pool_t pool,
+ const char *query, unsigned int *affected_rows);
+ const char *sql_statement_get_log_query(struct sql_statement *stmt);
+-const char *sql_statement_get_query(struct sql_statement *stmt);
++int sql_statement_get_query(struct sql_statement *stmt,
++ const char **query_r, const char **error_r);
+
+ void sql_connection_log_finished(struct sql_db *db);
+ struct event_passthrough *
+Index: trixie/src/lib-sql/sql-api.c
+===================================================================
+--- trixie.orig/src/lib-sql/sql-api.c
++++ trixie/src/lib-sql/sql-api.c
+@@ -98,7 +98,6 @@ static const struct sql_result_vfuncs sq
+ .get_error = sql_result_error_get_error,
+ };
+
+-static struct sql_result *sql_result_new_error(const char *error) ATTR_UNUSED;
+ static struct sql_result *sql_result_new_error(const char *error)
+ {
+ struct sql_result_error *result = i_new(struct sql_result_error, 1);
+@@ -327,9 +326,10 @@ void sql_disconnect(struct sql_db *db)
+ db->v.disconnect(db);
+ }
+
+-const char *sql_escape_string(struct sql_db *db, const char *string)
++int sql_escape_string(struct sql_db *db, const char *string,
++ const char **output_r, const char **error_r)
+ {
+- return db->v.escape_string(db, string);
++ return db->v.escape_string(db, string, output_r, error_r);
+ }
+
+ const char *sql_escape_blob(struct sql_db *db,
+@@ -391,19 +391,25 @@ default_sql_statement_init_prepared(stru
+
+ const char *sql_statement_get_log_query(struct sql_statement *stmt)
+ {
++ const char *query, *error;
+ if (stmt->no_log_expanded_values)
+ return stmt->query_template;
+- return sql_statement_get_query(stmt);
++ if (sql_statement_get_query(stmt, &query, &error) < 0)
++ return stmt->query_template;
++ return query;
+ }
+
+-const char *sql_statement_get_query(struct sql_statement *stmt)
++int sql_statement_get_query(struct sql_statement *stmt,
++ const char **query_r, const char **error_r)
+ {
+- string_t *query = t_str_new(128);
++ string_t *query = str_new(default_pool, 128);
+ const char *const *args;
+- unsigned int args_count, arg_pos = 0;
++ const bool *need_escaping_flags;
++ unsigned int args_count, need_escaping_count, arg_pos = 0;
+ const char *p0, *p1;
+
+ args = array_get(&stmt->args, &args_count);
++ need_escaping_flags = array_get(&stmt->args_need_escaping, &need_escaping_count);
+ p0 = stmt->query_template;
+ while ((p1 = strchr(p0, '?')) != NULL) {
+ /* append until ? */
+@@ -413,7 +419,31 @@ const char *sql_statement_get_query(stru
+ i_panic("lib-sql: Missing bind for arg #%u in statement: %s",
+ arg_pos, stmt->query_template);
+ }
+- str_append(query, args[arg_pos++]);
++ if (arg_pos < need_escaping_count && need_escaping_flags[arg_pos]) {
++ const char *escaped;
++
++ /* Escape in a nested data stack frame so the
++ driver's temporary escape buffer is freed
++ immediately. The escaped value is appended to the
++ heap-allocated query before the frame is popped. */
++ T_BEGIN {
++ if (sql_escape_string(stmt->db, args[arg_pos],
++ &escaped, error_r) < 0)
++ escaped = NULL;
++ else {
++ str_append_c(query, '\'');
++ str_append(query, escaped);
++ str_append_c(query, '\'');
++ }
++ } T_END_PASS_STR_IF(escaped == NULL, error_r);
++ if (escaped == NULL) {
++ str_free(&query);
++ return -1;
++ }
++ } else {
++ str_append(query, args[arg_pos]);
++ }
++ arg_pos++;
+ p0 = p1 + 1;
+ }
+ str_append(query, p0);
+@@ -422,23 +452,37 @@ const char *sql_statement_get_query(stru
+ i_panic("lib-sql: Too many bind args (%u) for statement: %s",
+ args_count, stmt->query_template);
+ }
+- return str_c(query);
++ *query_r = t_strdup(str_c(query));
++ str_free(&query);
++ return 0;
+ }
+
+ static void
+ default_sql_statement_query(struct sql_statement *stmt,
+ sql_query_callback_t *callback, void *context)
+ {
+- sql_query(stmt->db, sql_statement_get_query(stmt),
+- callback, context);
++ const char *query, *error;
++ if (sql_statement_get_query(stmt, &query, &error) < 0) {
++ sql_query_callback_delayed(stmt->db,
++ sql_result_new_error(error),
++ callback, context);
++ pool_unref(&stmt->pool);
++ return;
++ }
++ sql_query(stmt->db, query, callback, context);
+ pool_unref(&stmt->pool);
+ }
+
+ static struct sql_result *
+ default_sql_statement_query_s(struct sql_statement *stmt)
+ {
+- struct sql_result *result =
+- sql_query_s(stmt->db, sql_statement_get_query(stmt));
++ const char *query, *error;
++ if (sql_statement_get_query(stmt, &query, &error) < 0) {
++ struct sql_result *result = sql_result_new_error(error);
++ pool_unref(&stmt->pool);
++ return result;
++ }
++ struct sql_result *result = sql_query_s(stmt->db, query);
+ pool_unref(&stmt->pool);
+ return result;
+ }
+@@ -447,8 +491,14 @@ static void default_sql_update_stmt(stru
+ struct sql_statement *stmt,
+ unsigned int *affected_rows)
+ {
+- ctx->db->v.update(ctx, sql_statement_get_query(stmt),
+- affected_rows);
++ const char *query, *error;
++ if (sql_statement_get_query(stmt, &query, &error) < 0) {
++ if (ctx->failed_error == NULL)
++ ctx->failed_error = i_strdup(error);
++ pool_unref(&stmt->pool);
++ return;
++ }
++ ctx->db->v.update(ctx, query, affected_rows);
+ pool_unref(&stmt->pool);
+ }
+
+@@ -487,6 +537,7 @@ sql_statement_init_fields(struct sql_sta
+ {
+ stmt->db = db;
+ p_array_init(&stmt->args, stmt->pool, 8);
++ p_array_init(&stmt->args_need_escaping, stmt->pool, 8);
+ }
+
+ struct sql_statement *
+@@ -545,10 +596,10 @@ void sql_statement_set_no_log_expanded_v
+ void sql_statement_bind_str(struct sql_statement *stmt,
+ unsigned int column_idx, const char *value)
+ {
+- const char *escaped_value =
+- p_strdup_printf(stmt->pool, "'%s'",
+- sql_escape_string(stmt->db, value));
+- array_idx_set(&stmt->args, column_idx, &escaped_value);
++ const char *value_dup = p_strdup(stmt->pool, value);
++ array_idx_set(&stmt->args, column_idx, &value_dup);
++ bool needs_escaping = TRUE;
++ array_idx_set(&stmt->args_need_escaping, column_idx, &needs_escaping);
+
+ if (stmt->db->v.statement_bind_str != NULL)
+ stmt->db->v.statement_bind_str(stmt, column_idx, value);
+@@ -908,6 +959,13 @@ void sql_transaction_commit(struct sql_t
+ struct sql_db *db = ctx->db;
+ *_ctx = NULL;
+
++ if (ctx->failed_error != NULL) {
++ sql_commit_schedule_delayed(db, ctx->failed_error, callback, context);
++ i_free(ctx->failed_error);
++ ctx->db->v.transaction_rollback(ctx);
++ return;
++ }
++
+ if (ctx->db->v.transaction_commit != NULL) {
+ ctx->db->v.transaction_commit(ctx, callback, context);
+ return;
+@@ -924,6 +982,12 @@ int sql_transaction_commit_s(struct sql_
+ struct sql_transaction_context *ctx = *_ctx;
+
+ *_ctx = NULL;
++ if (ctx->failed_error != NULL) {
++ *error_r = t_strdup(ctx->failed_error);
++ i_free(ctx->failed_error);
++ ctx->db->v.transaction_rollback(ctx);
++ return -1;
++ }
+ return ctx->db->v.transaction_commit_s(ctx, error_r);
+ }
+
+@@ -932,6 +996,7 @@ void sql_transaction_rollback(struct sql
+ struct sql_transaction_context *ctx = *_ctx;
+
+ *_ctx = NULL;
++ i_free(ctx->failed_error);
+ ctx->db->v.transaction_rollback(ctx);
+ }
+
+Index: trixie/src/lib-sql/sql-api.h
+===================================================================
+--- trixie.orig/src/lib-sql/sql-api.h
++++ trixie/src/lib-sql/sql-api.h
+@@ -114,7 +114,8 @@ int sql_connect(struct sql_db *db);
+ void sql_disconnect(struct sql_db *db);
+
+ /* Escape the given string if needed and return it. */
+-const char *sql_escape_string(struct sql_db *db, const char *string);
++int sql_escape_string(struct sql_db *db, const char *string,
++ const char **output_r, const char **error_r);
+ /* Escape the given data as a string. */
+ const char *sql_escape_blob(struct sql_db *db,
+ const unsigned char *data, size_t size);
--- /dev/null
+From 8157e395df6b19891cea807fbb1f59e9f8b5a30a Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 7 May 2026 10:58:24 +0000
+Subject: [PATCH 5/5] submission-login: submission-proxy - Recognize 421 4.7.0
+ as connection-limit reply
+
+When the backend signals that the user's connection limit has been
+reached, do not classify the response as a generic temporary
+authentication failure (which causes the proxy to reconnect). Map it
+instead to LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED, which
+suppresses retries and reports the failure as proxy_dest_connection_limit
+in the login_aborted event/log line. This mirrors what imap-login does
+for the [LIMIT] response code and pop3-login for [IN-USE].
+
+Other 421 replies (typically server shutdown / fatal error) are mapped
+to LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED rather than AUTH_TEMPFAIL: 421
+means "closing transmission channel" (RFC 5321 Section 4.2.1), so a
+reconnect on the same destination is unlikely to help and should not
+happen automatically.
+---
+ src/submission-login/submission-proxy.c | 16 +++++++++++++++-
+ 1 file changed, 15 insertions(+), 1 deletion(-)
+
+Index: dovecot/src/submission-login/submission-proxy.c
+===================================================================
+--- dovecot.orig/src/submission-login/submission-proxy.c
++++ dovecot/src/submission-login/submission-proxy.c
+@@ -694,7 +694,21 @@ int submission_proxy_parse_line(struct c
+ be using only Dovecot as their backend :) */
+ enum login_proxy_failure_type failure_type =
+ LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED;
+- if ((status / 100) == 4)
++ if (status == 421) {
++ /* The backend won't accept further AUTH attempts on this
++ connection. If it signaled 4.7.0, the user's connection
++ limit (mail_max_userip_connections) was reached; report
++ that specifically. Otherwise treat as a generic auth
++ reply (e.g. backend shutting down). The reply was
++ prepared from the backend's 421 line; forward it to the
++ client and detach the AUTH command. */
++ failure_type = (null_strcmp(enh_code, "4.7.0") == 0) ?
++ LOGIN_PROXY_FAILURE_TYPE_AUTH_LIMIT_REACHED_REPLIED :
++ LOGIN_PROXY_FAILURE_TYPE_AUTH_REPLIED;
++ i_assert(subm_client->proxy_reply != NULL);
++ smtp_server_reply_submit(subm_client->proxy_reply);
++ subm_client->pending_auth = NULL;
++ } else if ((status / 100) == 4)
+ failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL;
+ else if (!submission_proxy_handle_redirect(
+ client, status, enh_code, text, &failure_type, &text)) {
--- /dev/null
+From abb51a4040fa21314c54a675cf35c4103e92da9c Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Fri, 29 May 2026 09:28:40 +0300
+Subject: [PATCH 6/6] auth: passdb_sql: connect before expanding query
+ variables
+
+sql_lookup_pass() calls settings_get_params() with an SQL escape func that
+requires a live connection. If the DB is disconnected, the error propagated
+as "Failed to parse configuration" instead of a DB connectivity error.
+
+Call sql_connect() first; fail with "Not connected to database" directly
+if it returns -1.
+---
+ src/auth/passdb-sql.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+Index: trixie/src/auth/passdb-sql.c
+===================================================================
+--- trixie.orig/src/auth/passdb-sql.c
++++ trixie/src/auth/passdb-sql.c
+@@ -183,6 +183,14 @@ static void sql_lookup_pass(struct passd
+ const struct passdb_sql_settings *set;
+ const char *error;
+
++ if (sql_connect(module->db) < 0) {
++ e_error(authdb_event(sql_request->auth_request),
++ "Not connected to database");
++ sql_request->callback.verify_plain(
++ PASSDB_RESULT_INTERNAL_FAILURE,
++ sql_request->auth_request);
++ return;
++ }
+ const struct settings_get_params params = {
+ .escape_func = passdb_sql_escape,
+ .escape_context = module->db,
--- /dev/null
+From 5ea530ea3677f5fe9d0a7040af87623f3f7af7ce Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 16 Apr 2026 17:38:53 +0200
+Subject: [PATCH 6/6] imap: Reset DEFLATE dictionary after every tagged reply
+
+Prevents CRIME-style cross-command compression oracle attacks
+(CVE-class: compression side-channel). Without this fix an observer
+who can inject chosen plaintext into one IMAP command's response can
+measure the compressed size of a subsequent command's response and
+determine whether the secret content matches the injected plaintext.
+
+After each tagged response line is sent, the DEFLATE compression
+dictionary is reset via Z_FULL_FLUSH so that the compression history
+from one command cannot influence the compressed size of the next.
+
+For direct compression (imap_compress_on_proxy=no) the reset is applied
+to the local ostream. For proxy-mode compression
+(imap_compress_on_proxy=yes) a "dict_reset" command is sent over the
+multiplex side channel to the imap-login process.
+---
+ src/imap/imap-client.c | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+diff --git a/src/imap/imap-client.c b/src/imap/imap-client.c
+index 4c8115f9a0..c44a4d810a 100644
+--- a/src/imap/imap-client.c
++++ b/src/imap/imap-client.c
+@@ -12,6 +12,7 @@
+ #include "istream-concat.h"
+ #include "ostream.h"
+ #include "ostream-multiplex.h"
++#include "ostream-zlib.h"
+ #include "time-util.h"
+ #include "settings.h"
+ #include "master-service.h"
+@@ -712,6 +713,22 @@ client_default_send_tagline(struct client_command_context *cmd, const char *data
+ } T_END;
+
+ client->last_output = ioloop_time;
++
++ /* Reset the DEFLATE compression dictionary after every tagged reply so
++ that cross-command compression correlation attacks (CRIME-style) are
++ not possible. For proxy-mode compression the reset is forwarded to
++ the imap-login process via the side channel; for direct compression
++ it is applied to the local ostream immediately. */
++ if (client->compress_handler != NULL) {
++ if (client->multiplex_output != NULL &&
++ client->set->imap_compress_on_proxy) {
++ i_assert(client->side_channel_output != NULL);
++ o_stream_nsend_str(client->side_channel_output,
++ "dict_reset\n");
++ } else {
++ o_stream_deflate_reset_dict(client->output);
++ }
++ }
+ }
+
+ static int
+--
+2.47.3
+
--- /dev/null
+From ed3c3d25b3ac571925f19b9b4d0dd8e8a718227e Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 13 Apr 2026 12:52:15 +0200
+Subject: [PATCH 06/12] lib-index: Rename crc32 variables/parameters to hash in
+ strmap
+
+Pure rename - no functional change. The stored 32-bit value is still
+produced by crc32_str_nonzero(); only the identifiers change to prepare
+for swapping the hash algorithm in the next commit.
+
+recs_crc32 -> recs_hash, hash_key.crc32 -> hash_key.hash,
+crc32_r -> hash_r, local crc32 -> hash, *crc32 -> *hashes.
+---
+ src/lib-index/mail-index-strmap.c | 84 +++++++++++++++----------------
+ 1 file changed, 42 insertions(+), 42 deletions(-)
+
+diff --git a/src/lib-index/mail-index-strmap.c b/src/lib-index/mail-index-strmap.c
+index 9ce2de9103..254080dbb7 100644
+--- a/src/lib-index/mail-index-strmap.c
++++ b/src/lib-index/mail-index-strmap.c
+@@ -31,7 +31,7 @@ struct mail_index_strmap_view {
+ struct mail_index_view *view;
+
+ ARRAY_TYPE(mail_index_strmap_rec) recs;
+- ARRAY(uint32_t) recs_crc32;
++ ARRAY(uint32_t) recs_hash;
+ struct hash2_table *hash;
+
+ mail_index_strmap_key_cmp_t *key_compare;
+@@ -74,7 +74,7 @@ struct mail_index_strmap_view_sync {
+
+ struct mail_index_strmap_hash_key {
+ const char *str;
+- uint32_t crc32;
++ uint32_t hash;
+ };
+
+ /* renumber the string indexes when highest string idx becomes larger than
+@@ -113,7 +113,7 @@ mail_index_strmap_init(struct mail_index *index, const char *suffix)
+
+ static bool
+ mail_index_strmap_read_rec_next(struct mail_index_strmap_read_context *ctx,
+- uint32_t *crc32_r);
++ uint32_t *hash_r);
+
+ static void
+ mail_index_strmap_set_syscall_error(struct mail_index_strmap *strmap,
+@@ -162,7 +162,7 @@ static unsigned int mail_index_strmap_hash_key(const void *_key)
+ {
+ const struct mail_index_strmap_hash_key *key = _key;
+
+- return key->crc32;
++ return key->hash;
+ }
+
+ static bool
+@@ -197,7 +197,7 @@ mail_index_strmap_view_open(struct mail_index_strmap *strmap,
+ view->next_str_idx = 1;
+
+ i_array_init(&view->recs, 64);
+- i_array_init(&view->recs_crc32, 64);
++ i_array_init(&view->recs_hash, 64);
+ view->hash = hash2_create(0, sizeof(struct mail_index_strmap_rec),
+ mail_index_strmap_hash_key,
+ mail_index_strmap_hash_cmp, view);
+@@ -212,7 +212,7 @@ void mail_index_strmap_view_close(struct mail_index_strmap_view **_view)
+
+ *_view = NULL;
+ array_free(&view->recs);
+- array_free(&view->recs_crc32);
++ array_free(&view->recs_hash);
+ hash2_destroy(&view->hash);
+ i_free(view);
+ }
+@@ -226,7 +226,7 @@ static void mail_index_strmap_view_reset(struct mail_index_strmap_view *view)
+ {
+ view->remap_cb(NULL, 0, 0, view->cb_context);
+ array_clear(&view->recs);
+- array_clear(&view->recs_crc32);
++ array_clear(&view->recs_hash);
+ hash2_clear(view->hash);
+
+ view->last_added_uid = 0;
+@@ -410,13 +410,13 @@ mail_index_strmap_uid_exists(struct mail_index_strmap_read_context *ctx,
+
+ static int
+ mail_index_strmap_read_rec_first(struct mail_index_strmap_read_context *ctx,
+- uint32_t *crc32_r)
++ uint32_t *hash_r)
+ {
+ size_t size;
+ uint32_t n, i, count, str_idx;
+ int ret;
+
+- /* <uid> <n> <crc32>*count <str_idx>*count
++ /* <uid> <n> <hash>*count <str_idx>*count
+ where
+ n = 0 -> count=1 (only Message-ID:)
+ n = 1 -> count=2 (Message-ID: + In-Reply-To:)
+@@ -426,11 +426,11 @@ mail_index_strmap_read_rec_first(struct mail_index_strmap_read_context *ctx,
+ return -1;
+ count = n < 2 ? n + 1 : n;
+ /* check that rec_size fits */
+- if (UINT_MAX / (sizeof(ctx->rec.str_idx) + sizeof(*crc32_r)) < count)
++ if (UINT_MAX / (sizeof(ctx->rec.str_idx) + sizeof(*hash_r)) < count)
+ return -1;
+ ctx->view->total_ref_count += count;
+
+- ctx->rec_size = count * (sizeof(ctx->rec.str_idx) + sizeof(*crc32_r));
++ ctx->rec_size = count * (sizeof(ctx->rec.str_idx) + sizeof(*hash_r));
+ ret = mail_index_strmap_uid_exists(ctx, ctx->rec.uid);
+ if (ret < 0)
+ return -1;
+@@ -454,10 +454,10 @@ mail_index_strmap_read_rec_first(struct mail_index_strmap_read_context *ctx,
+ /* everything exists. save it. FIXME: these ref_index values
+ are thread index specific, perhaps something more generic
+ should be used some day */
+- ctx->end = ctx->data + count * sizeof(*crc32_r);
++ ctx->end = ctx->data + count * sizeof(*hash_r);
+
+ ctx->next_ref_index = 0;
+- if (!mail_index_strmap_read_rec_next(ctx, crc32_r))
++ if (!mail_index_strmap_read_rec_next(ctx, hash_r))
+ i_unreached();
+ ctx->next_ref_index = n == 1 ? 1 : 2;
+ return 1;
+@@ -465,7 +465,7 @@ mail_index_strmap_read_rec_first(struct mail_index_strmap_read_context *ctx,
+
+ static bool
+ mail_index_strmap_read_rec_next(struct mail_index_strmap_read_context *ctx,
+- uint32_t *crc32_r)
++ uint32_t *hash_r)
+ {
+ if (ctx->data == ctx->end) {
+ i_stream_skip(ctx->view->strmap->input, ctx->rec_size);
+@@ -478,7 +478,7 @@ mail_index_strmap_read_rec_next(struct mail_index_strmap_read_context *ctx,
+
+ /* read the record contents */
+ memcpy(&ctx->rec.str_idx, ctx->str_idx_base, sizeof(ctx->rec.str_idx));
+- memcpy(crc32_r, ctx->data, sizeof(*crc32_r));
++ memcpy(hash_r, ctx->data, sizeof(*hash_r));
+
+ ctx->rec.ref_index = ctx->next_ref_index++;
+
+@@ -486,7 +486,7 @@ mail_index_strmap_read_rec_next(struct mail_index_strmap_read_context *ctx,
+ ctx->highest_str_idx = ctx->rec.str_idx;
+
+ /* get to the next record */
+- ctx->data += sizeof(*crc32_r);
++ ctx->data += sizeof(*hash_r);
+ ctx->str_idx_base += sizeof(ctx->rec.str_idx);
+ return TRUE;
+ }
+@@ -547,12 +547,12 @@ strmap_read_block_init(struct mail_index_strmap_view *view,
+
+ static int
+ strmap_read_block_next(struct mail_index_strmap_read_context *ctx,
+- uint32_t *crc32_r)
++ uint32_t *hash_r)
+ {
+ uint32_t uid_diff;
+ int ret;
+
+- if (mail_index_strmap_read_rec_next(ctx, crc32_r))
++ if (mail_index_strmap_read_rec_next(ctx, hash_r))
+ return 1;
+
+ /* get next UID */
+@@ -565,7 +565,7 @@ strmap_read_block_next(struct mail_index_strmap_read_context *ctx,
+ return -1;
+
+ ctx->rec.uid += uid_diff;
+- ret = mail_index_strmap_read_rec_first(ctx, crc32_r);
++ ret = mail_index_strmap_read_rec_first(ctx, hash_r);
+ } while (ret == 0);
+ return ret;
+ }
+@@ -635,12 +635,12 @@ strmap_view_sync_handle_conflict(struct mail_index_strmap_read_context *ctx,
+
+ static int
+ strmap_view_sync_block_check_conflicts(struct mail_index_strmap_read_context *ctx,
+- uint32_t crc32)
++ uint32_t hash)
+ {
+ struct mail_index_strmap_rec *hash_rec;
+ struct hash2_iter iter;
+
+- if (crc32 == 0) {
++ if (hash == 0) {
+ /* unique string - there are no conflicts */
+ return 0;
+ }
+@@ -657,9 +657,9 @@ strmap_view_sync_block_check_conflicts(struct mail_index_strmap_read_context *ct
+ strmap index until X has been expunged. */
+ i_zero(&iter);
+ while ((hash_rec = hash2_iterate(ctx->view->hash,
+- crc32, &iter)) != NULL &&
++ hash, &iter)) != NULL &&
+ hash_rec->str_idx != ctx->rec.str_idx) {
+- /* CRC32 matches, but string index doesn't */
++ /* hash matches, but string index doesn't */
+ if (!strmap_view_sync_handle_conflict(ctx, hash_rec, &iter)) {
+ ctx->lost_expunged_uid = hash_rec->uid;
+ return -1;
+@@ -672,10 +672,10 @@ static int
+ mail_index_strmap_view_sync_block(struct mail_index_strmap_read_context *ctx)
+ {
+ struct mail_index_strmap_rec *hash_rec;
+- uint32_t crc32, prev_uid = 0;
++ uint32_t hash, prev_uid = 0;
+ int ret;
+
+- while ((ret = strmap_read_block_next(ctx, &crc32)) > 0) {
++ while ((ret = strmap_read_block_next(ctx, &hash)) > 0) {
+ if (ctx->rec.uid <= ctx->view->last_added_uid) {
+ if (ctx->rec.uid < ctx->view->last_added_uid ||
+ prev_uid != ctx->rec.uid) {
+@@ -685,7 +685,7 @@ mail_index_strmap_view_sync_block(struct mail_index_strmap_read_context *ctx)
+ }
+ prev_uid = ctx->rec.uid;
+
+- if (strmap_view_sync_block_check_conflicts(ctx, crc32) < 0) {
++ if (strmap_view_sync_block_check_conflicts(ctx, hash) < 0) {
+ ret = -1;
+ break;
+ }
+@@ -693,10 +693,10 @@ mail_index_strmap_view_sync_block(struct mail_index_strmap_read_context *ctx)
+
+ /* add the record to records array */
+ array_push_back(&ctx->view->recs, &ctx->rec);
+- array_push_back(&ctx->view->recs_crc32, &crc32);
++ array_push_back(&ctx->view->recs_hash, &hash);
+
+ /* add a separate copy of the record to hash */
+- hash_rec = hash2_insert_hash(ctx->view->hash, crc32);
++ hash_rec = hash2_insert_hash(ctx->view->hash, hash);
+ memcpy(hash_rec, &ctx->rec, sizeof(*hash_rec));
+ }
+ return strmap_read_block_deinit(ctx, ret, TRUE);
+@@ -764,7 +764,7 @@ void mail_index_strmap_view_sync_add(struct mail_index_strmap_view_sync *sync,
+ ref_index > view->last_ref_index));
+
+ hash_key.str = key;
+- hash_key.crc32 = crc32_str_nonzero(key);
++ hash_key.hash = crc32_str_nonzero(key);
+
+ old_rec = hash2_lookup(view->hash, &hash_key);
+ if (old_rec != NULL) {
+@@ -781,7 +781,7 @@ void mail_index_strmap_view_sync_add(struct mail_index_strmap_view_sync *sync,
+ rec->ref_index = ref_index;
+ rec->str_idx = str_idx;
+ array_push_back(&view->recs, rec);
+- array_push_back(&view->recs_crc32, &hash_key.crc32);
++ array_push_back(&view->recs_hash, &hash_key.hash);
+
+ view->last_added_uid = uid;
+ view->last_ref_index = ref_index;
+@@ -802,7 +802,7 @@ void mail_index_strmap_view_sync_add_unique(struct mail_index_strmap_view_sync *
+ rec.ref_index = ref_index;
+ rec.str_idx = view->next_str_idx++;
+ array_push_back(&view->recs, &rec);
+- array_append_zero(&view->recs_crc32);
++ array_append_zero(&view->recs_hash);
+
+ view->last_added_uid = uid;
+ view->last_ref_index = ref_index;
+@@ -820,7 +820,7 @@ static void mail_index_strmap_view_renumber(struct mail_index_strmap_view *view)
+ {
+ struct mail_index_strmap_read_context ctx;
+ struct mail_index_strmap_rec *recs, *hash_rec;
+- uint32_t prev_uid, str_idx, *recs_crc32, *renumber_map;
++ uint32_t prev_uid, str_idx, *recs_hash, *renumber_map;
+ unsigned int i, dest, count, count2;
+ int ret;
+
+@@ -833,7 +833,7 @@ static void mail_index_strmap_view_renumber(struct mail_index_strmap_view *view)
+ renumber_map = i_new(uint32_t, view->next_str_idx);
+ str_idx = 0; prev_uid = 0;
+ recs = array_get_modifiable(&view->recs, &count);
+- recs_crc32 = array_get_modifiable(&view->recs_crc32, &count2);
++ recs_hash = array_get_modifiable(&view->recs_hash, &count2);
+ i_assert(count == count2);
+
+ for (i = dest = 0; i < count; ) {
+@@ -856,13 +856,13 @@ static void mail_index_strmap_view_renumber(struct mail_index_strmap_view *view)
+ renumber_map[recs[i].str_idx] = ++str_idx;
+ if (i != dest) {
+ recs[dest] = recs[i];
+- recs_crc32[dest] = recs_crc32[i];
++ recs_hash[dest] = recs_hash[i];
+ }
+ i++; dest++;
+ }
+ i_assert(renumber_map[0] == 0);
+ array_delete(&view->recs, dest, i-dest);
+- array_delete(&view->recs_crc32, dest, i-dest);
++ array_delete(&view->recs_hash, dest, i-dest);
+ mail_index_strmap_zero_terminate(view);
+
+ /* notify caller of the renumbering */
+@@ -875,7 +875,7 @@ static void mail_index_strmap_view_renumber(struct mail_index_strmap_view *view)
+ hash2_clear(view->hash);
+ for (i = 0; i < count; i++) {
+ recs[i].str_idx = renumber_map[recs[i].str_idx];
+- hash_rec = hash2_insert_hash(view->hash, recs_crc32[i]);
++ hash_rec = hash2_insert_hash(view->hash, recs_hash[i]);
+ memcpy(hash_rec, &recs[i], sizeof(*hash_rec));
+ }
+
+@@ -889,7 +889,7 @@ static void mail_index_strmap_write_block(struct mail_index_strmap_view *view,
+ unsigned int i, uint32_t base_uid)
+ {
+ const struct mail_index_strmap_rec *recs;
+- const uint32_t *crc32;
++ const uint32_t *hashes;
+ unsigned int j, n, count, count2, uid_rec_count;
+ uint32_t block_size;
+ uint8_t *p, packed[MAIL_INDEX_PACK_MAX_SIZE*2];
+@@ -902,7 +902,7 @@ static void mail_index_strmap_write_block(struct mail_index_strmap_view *view,
+
+ /* write records */
+ recs = array_get(&view->recs, &count);
+- crc32 = array_get(&view->recs_crc32, &count2);
++ hashes = array_get(&view->recs_hash, &count2);
+ i_assert(count == count2);
+ while (i < count) {
+ /* @UNSAFE: <uid diff> */
+@@ -919,7 +919,7 @@ static void mail_index_strmap_write_block(struct mail_index_strmap_view *view,
+ }
+ view->total_ref_count += uid_rec_count;
+
+- /* <n> <crc32>*count <str_idx>*count -
++ /* <n> <hash>*count <str_idx>*count -
+ FIXME: thread index specific code */
+ i_assert(recs[i].ref_index == 0);
+ if (uid_rec_count == 1) {
+@@ -938,7 +938,7 @@ static void mail_index_strmap_write_block(struct mail_index_strmap_view *view,
+ mail_index_pack_num(&p, n);
+ o_stream_nsend(output, packed, p-packed);
+ for (j = 0; j < uid_rec_count; j++)
+- o_stream_nsend(output, &crc32[i+j], sizeof(crc32[i+j]));
++ o_stream_nsend(output, &hashes[i+j], sizeof(hashes[i+j]));
+ for (j = 0; j < uid_rec_count; j++) {
+ i_assert(j < 2 || recs[i+j].ref_index == j+1);
+ o_stream_nsend(output, &recs[i+j].str_idx,
+@@ -1095,7 +1095,7 @@ mail_index_strmap_write_append(struct mail_index_strmap_view *view)
+ const struct mail_index_strmap_rec *old_recs;
+ unsigned int i, old_count;
+ struct ostream *output;
+- uint32_t crc32, next_uid;
++ uint32_t hash, next_uid;
+ bool full_block;
+ int ret;
+
+@@ -1134,7 +1134,7 @@ mail_index_strmap_write_append(struct mail_index_strmap_view *view)
+ full_block = TRUE; ret = 0;
+ while (i < old_count &&
+ (ret = strmap_read_block_init(view, &ctx)) > 0) {
+- while ((ret = strmap_read_block_next(&ctx, &crc32)) > 0) {
++ while ((ret = strmap_read_block_next(&ctx, &hash)) > 0) {
+ if (ctx.rec.uid != old_recs[i].uid ||
+ ctx.rec.str_idx != old_recs[i].str_idx) {
+ /* mismatch */
+--
+2.47.3
+
--- /dev/null
+From 025233779751d7ba8cff7c615f8e229b604d92a5 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 3 Jun 2026 12:25:38 +0000
+Subject: [PATCH 6/7] lib-sieve: Log a warning when cumulative CPU exceeds the
+ limit
+
+sieve_binary_record_resource_usage() returned FALSE when the script's
+cumulative CPU time crossed sieve_max_cpu_time, but the only log line
+on that path was a debug-level "Updated cumulative resource usage"
+emitted on every recording, with no signal at the actual disable
+transition. Operators had to infer the disable from later "Failed to
+open script ... (cumulative resource limit exceeded)" errors on
+subsequent deliveries.
+
+Emit a warning at the transition point, including the cumulative CPU
+usage that triggered it.
+---
+ src/lib-sieve/sieve-binary.c | 17 ++++++++++++++++-
+ 1 file changed, 16 insertions(+), 1 deletion(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary.c
+@@ -248,6 +248,7 @@ bool sieve_binary_record_resource_usage(
+ struct sieve_binary *sbin, const struct sieve_resource_usage *rusage)
+ {
+ struct sieve_resource_usage rusage_total;
++ struct sieve_script *script;
+
+ if (sbin == NULL)
+ return TRUE;
+@@ -262,7 +263,21 @@ bool sieve_binary_record_resource_usage(
+ e_debug(sbin->event, "Updated cumulative resource usage: %s",
+ sieve_resource_usage_get_summary(&rusage_total));
+
+- return sieve_binary_check_resource_usage(sbin);
++ if (!sieve_resource_usage_is_excessive(sbin->svinst, &rusage_total))
++ return TRUE;
++
++ /* Cumulative resource usage is only tracked for personal scripts;
++ all callers gate this on the user's own script. */
++ script = sbin->script;
++ i_assert(script != NULL);
++ i_assert(sieve_storage_is_personal(script->storage));
++
++ e_warning(sbin->event,
++ "Personal sieve processing disabled for script '%s': "
++ "cumulative resource usage limit exceeded (%s)",
++ sieve_script_label(script),
++ sieve_resource_usage_get_summary(&rusage_total));
++ return FALSE;
+ }
+
+ void sieve_binary_set_resource_usage(struct sieve_binary *sbin,
--- /dev/null
+From 89ed3baf87b789f55d7513252ef2dead41cf4105 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Sun, 19 Apr 2026 15:57:08 +0300
+Subject: [PATCH 06/14] lib-storage/mbox: Explicitly disable the header block
+ size limit
+
+mbox_sync_parse_next_mail() appends each header's raw bytes into
+ctx->header and writes them back when rewriting the mbox file. The
+default MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE limit (10 MB) must not
+apply here: any truncation would corrupt the mbox on rewrite.
+Similarly, mbox_sync_parse_match_mail() feeds full raw header bytes
+into the MD5 verifier.
+
+Today the per-chunk hdr->value delivered by message_parse_header_next()
+is not clamped cumulatively, so the limit only bites on full_value and
+unknown headers pass through intact. That is about to change - a
+subsequent commit will clamp hdr->value cumulatively. Explicitly
+setting SIZE_MAX here locks in the intent and prevents a regression.
+
+Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
+---
+ src/lib-storage/index/mbox/mbox-sync-parse.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/src/lib-storage/index/mbox/mbox-sync-parse.c b/src/lib-storage/index/mbox/mbox-sync-parse.c
+index 90f032a017..660e9b63d0 100644
+--- a/src/lib-storage/index/mbox/mbox-sync-parse.c
++++ b/src/lib-storage/index/mbox/mbox-sync-parse.c
+@@ -479,6 +479,9 @@ int mbox_sync_parse_next_mail(struct istream *input,
+
+ line_start_pos = 0;
+ hdr_ctx = message_parse_header_init(input, NULL, 0);
++ /* Rewriting the mbox requires the full raw header bytes, so disable
++ the default per-header block size limit. */
++ message_parse_header_set_limit(hdr_ctx, SIZE_MAX);
+ while ((ret = message_parse_header_next(hdr_ctx, &hdr)) > 0) {
+ if (hdr->eoh) {
+ ctx->have_eoh = TRUE;
+@@ -576,6 +579,8 @@ bool mbox_sync_parse_match_mail(struct mbox_mailbox *mbox,
+ mbox_md5_ctx = mbox->md5_v.init();
+
+ hdr_ctx = message_parse_header_init(mbox->mbox_stream, NULL, 0);
++ /* MD5 matching must see the full raw header bytes. */
++ message_parse_header_set_limit(hdr_ctx, SIZE_MAX);
+ while ((ret = message_parse_header_next(hdr_ctx, &hdr)) > 0) {
+ if (hdr->eoh)
+ break;
+--
+2.47.3
+
--- /dev/null
+From ef45dd7c36562ef07cccf6cc4ad245e0041af456 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 30 Apr 2026 12:11:57 +0000
+Subject: [PATCH 07/12] lib-index: Add keyed xxh64 strmap format v2, gated by
+ config version
+
+The on-disk hash stored in the strmap file is now optionally a keyed
+xxh64_to_32() with a per-file random 64-bit IV stored in the file
+header. This replaces the previous plain crc32_str_nonzero() output
+and hardens the strmap against deliberately-collided message-id
+hashes. The v1 (crc32) format remains fully readable and writable
+so older configurations keep working without forced rebuilds.
+
+On-disk:
+ - v1 header is 8 bytes: version, 3 unused, uid_validity (unchanged).
+ - v2 header is 16 bytes: version, compat_flags, 2 unused, uid_validity,
+ hash_iv. The first 8 bytes of v2 align with v1 byte-for-byte, so
+ the open path reads 8 bytes, dispatches on version, and reads the
+ trailing 8 bytes only for v2.
+
+Runtime:
+ - strmap->enable_xxh64 selects the format used when creating a new
+ file (or recreating one whose uid_validity has changed).
+ - view->format_version follows the on-disk file when one exists; for
+ fresh files it follows the strmap-wide preference.
+ - Renumber recreate_write() preserves view->format_version, so we
+ never silently migrate an existing file across the threshold while
+ it is still in use - the migration only happens at open time.
+ - When enable_xxh64 is TRUE and the on-disk file is v1, open treats
+ it as a version mismatch: the file is unlinked and the next sync's
+ recreate_write() produces a v2 file. Below the threshold v1 stays
+ v1 indefinitely.
+ - strmap_hash_str() dispatches per-view, so v1 files keep using
+ crc32 hashes and v2 files use keyed xxh64 even in mixed setups.
+---
+ src/lib-index/mail-index-strmap.c | 106 ++++++++++++++++++++++++---
+ src/lib-index/mail-index-strmap.h | 17 ++++-
+ src/lib-master/Makefile.am | 1 +
+ src/lib-master/storage-version.h | 22 ++++++
+ src/lib-storage/index/index-thread.c | 12 ++-
+ 5 files changed, 143 insertions(+), 15 deletions(-)
+ create mode 100644 src/lib-master/storage-version.h
+
+Index: trixie/src/lib-index/mail-index-strmap.c
+===================================================================
+--- trixie.orig/src/lib-index/mail-index-strmap.c
++++ trixie/src/lib-index/mail-index-strmap.c
+@@ -8,6 +8,8 @@
+ #include "file-lock.h"
+ #include "file-dotlock.h"
+ #include "crc32.h"
++#include "randgen.h"
++#include "xxh64.h"
+ #include "safe-mkstemp.h"
+ #include "str.h"
+ #include "mail-index-private.h"
+@@ -24,6 +26,12 @@ struct mail_index_strmap {
+ struct file_lock *file_lock;
+ struct dotlock *dotlock;
+ struct dotlock_settings dotlock_settings;
++
++ /* If TRUE, new files are created in v2 format (xxh64-keyed hashes).
++ If FALSE, new files are created in v1 format (crc32). Existing
++ files of either version are always read; on rebuild they are
++ recreated in this preferred format. */
++ bool enable_xxh64;
+ };
+
+ struct mail_index_strmap_view {
+@@ -34,6 +42,14 @@ struct mail_index_strmap_view {
+ ARRAY(uint32_t) recs_hash;
+ struct hash2_table *hash;
+
++ /* On-disk format version that view's hash values are computed
++ against. v1 = crc32_str_nonzero, v2 = keyed xxh64. Set from the
++ file header when opening an existing file; otherwise from
++ strmap->enable_xxh64. */
++ uint8_t format_version;
++ /* Per-file random IV mixed into the xxh64 hash. Unused for v1. */
++ uint64_t hash_iv;
++
+ mail_index_strmap_key_cmp_t *key_compare;
+ mail_index_strmap_rec_cmp_t *rec_compare;
+ mail_index_strmap_remap_t *remap_cb;
+@@ -92,7 +108,8 @@ static const struct dotlock_settings def
+ };
+
+ struct mail_index_strmap *
+-mail_index_strmap_init(struct mail_index *index, const char *suffix)
++mail_index_strmap_init(struct mail_index *index, const char *suffix,
++ bool enable_xxh64)
+ {
+ struct mail_index_strmap *strmap;
+
+@@ -102,6 +119,7 @@ mail_index_strmap_init(struct mail_index
+ strmap->index = index;
+ strmap->path = i_strconcat(index->filepath, suffix, NULL);
+ strmap->fd = -1;
++ strmap->enable_xxh64 = enable_xxh64;
+
+ strmap->dotlock_settings = default_dotlock_settings;
+ strmap->dotlock_settings.use_excl_lock =
+@@ -198,6 +216,9 @@ mail_index_strmap_view_open(struct mail_
+
+ i_array_init(&view->recs, 64);
+ i_array_init(&view->recs_hash, 64);
++ view->format_version = strmap->enable_xxh64 ?
++ MAIL_INDEX_STRMAP_VERSION_V2 : MAIL_INDEX_STRMAP_VERSION_V1;
++ random_fill(&view->hash_iv, sizeof(view->hash_iv));
+ view->hash = hash2_create(0, sizeof(struct mail_index_strmap_rec),
+ mail_index_strmap_hash_key,
+ mail_index_strmap_hash_cmp, view);
+@@ -250,7 +271,7 @@ static int mail_index_strmap_open(struct
+ const struct mail_index_header *idx_hdr;
+ struct mail_index_strmap_header hdr;
+ const unsigned char *data;
+- size_t size;
++ size_t size, hdr_size;
+ int ret;
+
+ i_assert(strmap->fd == -1);
+@@ -263,7 +284,12 @@ static int mail_index_strmap_open(struct
+ return -1;
+ }
+ strmap->input = i_stream_create_fd(strmap->fd, SIZE_MAX);
+- ret = i_stream_read_bytes(strmap->input, &data, &size, sizeof(hdr));
++
++ /* Read the smaller v1 header first; bytes 0..7 have identical
++ layout in both formats so we can decide based on the version
++ byte before reading the v2 tail. */
++ ret = i_stream_read_bytes(strmap->input, &data, &size,
++ MAIL_INDEX_STRMAP_HEADER_V1_SIZE);
+ if (ret <= 0) {
+ if (ret < 0) {
+ mail_index_strmap_set_syscall_error(strmap, "read()");
+@@ -274,23 +300,55 @@ static int mail_index_strmap_open(struct
+ }
+ return ret;
+ }
+- memcpy(&hdr, data, sizeof(hdr));
++
++ i_zero(&hdr);
++ hdr_size = data[0] == MAIL_INDEX_STRMAP_VERSION_V2 ?
++ MAIL_INDEX_STRMAP_HEADER_V2_SIZE :
++ MAIL_INDEX_STRMAP_HEADER_V1_SIZE;
++ if (hdr_size > MAIL_INDEX_STRMAP_HEADER_V1_SIZE) {
++ ret = i_stream_read_bytes(strmap->input, &data, &size, hdr_size);
++ if (ret <= 0) {
++ if (ret < 0) {
++ mail_index_strmap_set_syscall_error(strmap, "read()");
++ mail_index_strmap_close(strmap);
++ } else {
++ mail_index_strmap_view_set_corrupted(view);
++ }
++ return ret;
++ }
++ }
++ memcpy(&hdr, data, hdr_size);
+
+ idx_hdr = mail_index_get_header(view->view);
+- if (hdr.version != MAIL_INDEX_STRMAP_VERSION ||
+- hdr.uid_validity != idx_hdr->uid_validity) {
++ uint8_t compat_flags = 0;
++#ifndef WORDS_BIGENDIAN
++ if (hdr.version >= MAIL_INDEX_STRMAP_VERSION_V2)
++ compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN;
++#endif
++ if ((hdr.version != MAIL_INDEX_STRMAP_VERSION_V1 &&
++ hdr.version != MAIL_INDEX_STRMAP_VERSION_V2) ||
++ hdr.compat_flags != compat_flags ||
++ hdr.uid_validity != idx_hdr->uid_validity ||
++ (strmap->enable_xxh64 &&
++ hdr.version < MAIL_INDEX_STRMAP_VERSION_V2)) {
+ /* need to rebuild. if we already had something in the strmap,
+- we can keep it. */
++ we can keep it. The enable_xxh64 case forces a v1 file to
++ be discarded so the next write creates a v2 file - we never
++ stay on v1 once the dovecot_storage_version threshold has
++ been reached. */
+ i_unlink(strmap->path);
+ mail_index_strmap_close(strmap);
+ return 0;
+ }
++ view->format_version = hdr.version;
++ view->hash_iv = hdr.version >= MAIL_INDEX_STRMAP_VERSION_V2 ?
++ hdr.hash_iv : 0;
+
+ /* we'll read the entire file from the beginning */
+ view->last_added_uid = 0;
+ view->last_read_uid = 0;
+ view->total_ref_count = 0;
+- view->last_read_block_offset = sizeof(struct mail_index_strmap_header);
++ view->last_read_block_offset = hdr_size;
+ view->next_str_idx = 1;
+
+ mail_index_strmap_view_reset(view);
+@@ -750,6 +808,17 @@ static inline uint32_t crc32_str_nonzero
+ return value == 0 ? 1 : value;
+ }
+
++static inline uint32_t
++strmap_hash_str(const struct mail_index_strmap_view *view, const char *str)
++{
++ if (view->format_version < MAIL_INDEX_STRMAP_VERSION_V2)
++ return crc32_str_nonzero(str);
++
++ uint32_t value = xxh64_to_32(xxh64_data(str, strlen(str), view->hash_iv));
++ /* 0 is reserved as a sentinel meaning "unique / no hash stored" */
++ return value == 0 ? 1 : value;
++}
++
+ void mail_index_strmap_view_sync_add(struct mail_index_strmap_view_sync *sync,
+ uint32_t uid, uint32_t ref_index,
+ const char *key)
+@@ -764,7 +833,7 @@ void mail_index_strmap_view_sync_add(str
+ ref_index > view->last_ref_index));
+
+ hash_key.str = key;
+- hash_key.hash = crc32_str_nonzero(key);
++ hash_key.hash = strmap_hash_str(view, key);
+
+ old_rec = hash2_lookup(view->hash, &hash_key);
+ if (old_rec != NULL) {
+@@ -971,14 +1040,29 @@ mail_index_strmap_recreate_write(struct
+ {
+ const struct mail_index_header *idx_hdr;
+ struct mail_index_strmap_header hdr;
++ size_t hdr_size;
+
+ idx_hdr = mail_index_get_header(view->view);
+
++ /* view->format_version was set either at view_open() time (from the
++ strmap-wide enable_xxh64 preference for fresh files) or in
++ mail_index_strmap_open() from the existing file's header. Reuse
++ it as-is so renumber recreates preserve the file's format. */
++
+ /* write header */
+ i_zero(&hdr);
+- hdr.version = MAIL_INDEX_STRMAP_VERSION;
++ hdr.version = view->format_version;
+ hdr.uid_validity = idx_hdr->uid_validity;
+- o_stream_nsend(output, &hdr, sizeof(hdr));
++ if (view->format_version >= MAIL_INDEX_STRMAP_VERSION_V2) {
++#ifndef WORDS_BIGENDIAN
++ hdr.compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN;
++#endif
++ hdr.hash_iv = view->hash_iv;
++ hdr_size = MAIL_INDEX_STRMAP_HEADER_V2_SIZE;
++ } else {
++ hdr_size = MAIL_INDEX_STRMAP_HEADER_V1_SIZE;
++ }
++ o_stream_nsend(output, &hdr, hdr_size);
+
+ view->total_ref_count = 0;
+ mail_index_strmap_write_block(view, output, 0, 1);
+Index: trixie/src/lib-index/mail-index-strmap.h
+===================================================================
+--- trixie.orig/src/lib-index/mail-index-strmap.h
++++ trixie/src/lib-index/mail-index-strmap.h
+@@ -7,12 +7,22 @@ struct mail_index;
+ struct mail_index_view;
+
+ struct mail_index_strmap_header {
+-#define MAIL_INDEX_STRMAP_VERSION 1
++/* On-disk format version. The header struct below describes version 2;
++ version 1 files use the same first 8 bytes (version + 3 unused + uid_validity)
++ without the trailing compat_flags / unused / hash_iv fields. */
++#define MAIL_INDEX_STRMAP_VERSION_V1 1
++#define MAIL_INDEX_STRMAP_VERSION_V2 2
+ uint8_t version;
+- uint8_t unused[3];
++ uint8_t compat_flags; /* enum mail_index_header_compat_flags, v2+ */
++ uint8_t unused[2];
+
+ uint32_t uid_validity;
++
++ /* v2+ fields - not present on disk for v1 files. */
++ uint64_t hash_iv; /* per-file random IV mixed into the xxh64 hash */
+ };
++#define MAIL_INDEX_STRMAP_HEADER_V1_SIZE 8
++#define MAIL_INDEX_STRMAP_HEADER_V2_SIZE sizeof(struct mail_index_strmap_header)
+
+ struct mail_index_strmap_rec {
+ uint32_t uid;
+@@ -40,7 +50,8 @@ typedef void mail_index_strmap_remap_t(c
+ unsigned int new_count, void *context);
+
+ struct mail_index_strmap *
+-mail_index_strmap_init(struct mail_index *index, const char *suffix);
++mail_index_strmap_init(struct mail_index *index, const char *suffix,
++ bool enable_xxh64);
+ void mail_index_strmap_deinit(struct mail_index_strmap **strmap);
+
+ /* Returns strmap records and hash that can be used for read-only access.
+Index: trixie/src/lib-master/Makefile.am
+===================================================================
+--- trixie.orig/src/lib-master/Makefile.am
++++ trixie/src/lib-master/Makefile.am
+@@ -39,6 +39,7 @@ headers = \
+ master-service-ssl.h \
+ service-settings.h \
+ stats-client.h \
++ storage-version.h \
+ syslog-util.h
+
+ pkginc_libdir=$(pkgincludedir)
+Index: trixie/src/lib-master/storage-version.h
+===================================================================
+--- /dev/null
++++ trixie/src/lib-master/storage-version.h
+@@ -0,0 +1,22 @@
++#ifndef STORAGE_VERSION_H
++#define STORAGE_VERSION_H
++
++#include "version.h"
++
++/* Returns TRUE if dovecot_storage_version selects the v2 (keyed xxh64)
++ mail_index_strmap on-disk format. */
++static inline bool
++storage_version_has_mail_index_strmap_v2(const char *version)
++{
++ if (version == NULL) {
++ /* unit test */
++ return TRUE;
++ }
++#ifdef DOVECOT_PRO_EDITION
++ return version_cmp(version, "3.1.6") >= 0;
++#else
++ return version_cmp(version, "2.4.0") >= 0;
++#endif
++}
++
++#endif
+Index: trixie/src/lib-storage/index/index-thread.c
+===================================================================
+--- trixie.orig/src/lib-storage/index/index-thread.c
++++ trixie/src/lib-storage/index/index-thread.c
+@@ -7,6 +7,9 @@
+ #include "bsearch-insert-pos.h"
+ #include "hash2.h"
+ #include "message-id.h"
++#include "master-service.h"
++#include "master-service-settings.h"
++#include "storage-version.h"
+ #include "mail-search.h"
+ #include "mail-search-build.h"
+ #include "mailbox-search-result-private.h"
+@@ -668,8 +671,15 @@ void index_thread_mailbox_opened(struct
+ box->v.close = mail_thread_mailbox_close;
+ box->v.free = mail_thread_mailbox_free;
+
++ const struct master_service_settings *master_set =
++ master_service_get_service_settings(master_service);
++ bool use_xxh64 = master_set != NULL &&
++ storage_version_has_mail_index_strmap_v2(
++ master_set->dovecot_storage_version);
++
+ tbox->strmap = mail_index_strmap_init(box->index,
+- MAIL_THREAD_INDEX_SUFFIX);
++ MAIL_THREAD_INDEX_SUFFIX,
++ use_xxh64);
+ tbox->next_msgid_idx = 1;
+
+ tbox->cache = i_new(struct mail_thread_cache, 1);
--- /dev/null
+From c5996d464e31ee9950fd40654e9fedcf9f99719e Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 20 Apr 2026 01:14:02 +0300
+Subject: [PATCH 07/14] lib-mail: istream-header-filter - Use container_of()
+ for struct casts
+
+Replace the C-style downcasts from struct istream_private/iostream_private
+to struct header_filter_istream with container_of(), matching the
+convention used by other istream implementations in tree.
+---
+ src/lib-mail/istream-header-filter.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/src/lib-mail/istream-header-filter.c b/src/lib-mail/istream-header-filter.c
+index ee94e4f82b..5fee9cf17b 100644
+--- a/src/lib-mail/istream-header-filter.c
++++ b/src/lib-mail/istream-header-filter.c
+@@ -60,7 +60,7 @@ static ssize_t i_stream_header_filter_read(struct istream_private *stream);
+ static void i_stream_header_filter_destroy(struct iostream_private *stream)
+ {
+ struct header_filter_istream *mstream =
+- (struct header_filter_istream *)stream;
++ container_of(stream, struct header_filter_istream, istream.iostream);
+
+ if (mstream->hdr_ctx != NULL)
+ message_parse_header_deinit(&mstream->hdr_ctx);
+@@ -470,7 +470,7 @@ handle_end_body_with_lf(struct header_filter_istream *mstream, ssize_t ret)
+ static ssize_t i_stream_header_filter_read(struct istream_private *stream)
+ {
+ struct header_filter_istream *mstream =
+- (struct header_filter_istream *)stream;
++ container_of(stream, struct header_filter_istream, istream);
+ uoff_t v_offset;
+ ssize_t ret;
+
+@@ -554,7 +554,7 @@ static void i_stream_header_filter_seek(struct istream_private *stream,
+ uoff_t v_offset, bool mark ATTR_UNUSED)
+ {
+ struct header_filter_istream *mstream =
+- (struct header_filter_istream *)stream;
++ container_of(stream, struct header_filter_istream, istream);
+
+ if (stream->istream.v_offset == v_offset) {
+ /* just reset the input buffer */
+@@ -604,7 +604,7 @@ static int
+ i_stream_header_filter_stat(struct istream_private *stream, bool exact)
+ {
+ struct header_filter_istream *mstream =
+- (struct header_filter_istream *)stream;
++ container_of(stream, struct header_filter_istream, istream);
+ const struct stat *st;
+ uoff_t old_offset;
+
+@@ -673,7 +673,7 @@ i_stream_header_filter_snapshot(struct istream_private *stream,
+ struct istream_snapshot *prev_snapshot)
+ {
+ struct header_filter_istream *mstream =
+- (struct header_filter_istream *)stream;
++ container_of(stream, struct header_filter_istream, istream);
+ struct header_filter_istream_snapshot *snapshot;
+
+ if (stream->buffer != mstream->hdr_buf->data) {
+--
+2.47.3
+
--- /dev/null
+From 800697eaa3b753d7a449fb7da62f41a21b02925e Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 3 Jun 2026 12:25:49 +0000
+Subject: [PATCH 7/7] lib-sieve: Log an info line when opening a CPU-disabled
+ script
+
+sieve_binary_check_executable() blocks execution when a script's
+persisted cumulative CPU usage already exceeds sieve_max_cpu_time, but
+only logged the block at debug level. With cumulative tracking now
+sticky across redeliveries via the per-user sieve-rusage file, the
+script can stay blocked indefinitely until the resource_usage_timeout
+decay or admin intervention; operators benefit from a per-attempt
+record of that block at info level.
+
+Promote the existing debug log to e_info and include the cumulative
+CPU usage. The caller continues to emit its own error message on the
+failed open (lda-sieve, imap-sieve, imap-filter-sieve plugin paths).
+---
+ src/lib-sieve/sieve-binary-file.c | 14 +++++++++++---
+ 1 file changed, 11 insertions(+), 3 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/sieve-binary-file.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/sieve-binary-file.c
++++ trixie/pigeonhole/src/lib-sieve/sieve-binary-file.c
+@@ -935,15 +935,23 @@ int sieve_binary_check_executable(struct
+ const char **client_error_r)
+ {
+ struct sieve_resource_usage rusage;
++ struct sieve_script *script = sbin->script;
+
+ *client_error_r = NULL;
+ sieve_error_args_init(&error_code_r, NULL);
+
+ sieve_binary_get_resource_usage(sbin, &rusage);
+ if (sieve_resource_usage_is_excessive(sbin->svinst, &rusage)) {
+- e_debug(sbin->event,
+- "Binary execution is blocked: "
+- "Cumulative resource usage limit exceeded");
++ /* Cumulative resource usage is only tracked for personal
++ scripts. */
++ i_assert(script != NULL);
++ i_assert(sieve_storage_is_personal(script->storage));
++
++ e_info(sbin->event,
++ "Personal sieve processing disabled for script '%s': "
++ "cumulative resource usage limit exceeded (%s)",
++ sieve_script_label(script),
++ sieve_resource_usage_get_summary(&rusage));
+ *error_code_r = SIEVE_ERROR_RESOURCE_LIMIT;
+ *client_error_r = "cumulative resource usage limit exceeded";
+ return 0;
--- /dev/null
+From 30ec9bdcb445011801229a825c1d5a443397463f Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 11 Jun 2026 20:02:15 +0000
+Subject: [PATCH] lib-sql: Run sql statement queries in their own data stack
+ frame
+
+---
+ src/lib-sql/sql-api.c | 62 +++++++++++++++++++++++++------------------
+ 1 file changed, 36 insertions(+), 26 deletions(-)
+
+Index: trixie/src/lib-sql/sql-api.c
+===================================================================
+--- trixie.orig/src/lib-sql/sql-api.c
++++ trixie/src/lib-sql/sql-api.c
+@@ -657,31 +657,37 @@ void sql_statement_query(struct sql_stat
+ struct sql_statement *stmt = *_stmt;
+ *_stmt = NULL;
+
+- if (stmt->db->v.statement_query != NULL)
+- stmt->db->v.statement_query(stmt, callback, context);
+- else if (stmt->db->v.statement_query_s != NULL) {
+- struct sql_db *db = stmt->db;
+- struct sql_query_result_delayed *cb =
+- i_new(struct sql_query_result_delayed, 1);
+- cb->db = db;
+- cb->callback = callback;
+- cb->context = context;
+- cb->result = sql_statement_query_s(&stmt);
+- cb->to = timeout_add_short(0, sql_query_delayed_callback, cb);
+- DLLIST_PREPEND(&db->query_delayed_list, cb);
+- } else
+- default_sql_statement_query(stmt, callback, context);
++ T_BEGIN {
++ if (stmt->db->v.statement_query != NULL)
++ stmt->db->v.statement_query(stmt, callback, context);
++ else if (stmt->db->v.statement_query_s != NULL) {
++ struct sql_db *db = stmt->db;
++ struct sql_query_result_delayed *cb =
++ i_new(struct sql_query_result_delayed, 1);
++ cb->db = db;
++ cb->callback = callback;
++ cb->context = context;
++ cb->result = sql_statement_query_s(&stmt);
++ cb->to = timeout_add_short(0, sql_query_delayed_callback, cb);
++ DLLIST_PREPEND(&db->query_delayed_list, cb);
++ } else
++ default_sql_statement_query(stmt, callback, context);
++ } T_END;
+ }
+
+ struct sql_result *sql_statement_query_s(struct sql_statement **_stmt)
+ {
+ struct sql_statement *stmt = *_stmt;
++ struct sql_result *result;
+
+ *_stmt = NULL;
+- if (stmt->db->v.statement_query_s != NULL)
+- return stmt->db->v.statement_query_s(stmt);
+- else
+- return default_sql_statement_query_s(stmt);
++ T_BEGIN {
++ if (stmt->db->v.statement_query_s != NULL)
++ result = stmt->db->v.statement_query_s(stmt);
++ else
++ result = default_sql_statement_query_s(stmt);
++ } T_END;
++ return result;
+ }
+
+ void sql_result_ref(struct sql_result *result)
+@@ -1011,10 +1017,12 @@ void sql_update_stmt(struct sql_transact
+ struct sql_statement *stmt = *_stmt;
+
+ *_stmt = NULL;
+- if (ctx->db->v.update_stmt != NULL)
+- ctx->db->v.update_stmt(ctx, stmt, NULL);
+- else
+- default_sql_update_stmt(ctx, stmt, NULL);
++ T_BEGIN {
++ if (ctx->db->v.update_stmt != NULL)
++ ctx->db->v.update_stmt(ctx, stmt, NULL);
++ else
++ default_sql_update_stmt(ctx, stmt, NULL);
++ } T_END;
+ }
+
+ void sql_update_get_rows(struct sql_transaction_context *ctx, const char *query,
+@@ -1030,10 +1038,12 @@ void sql_update_stmt_get_rows(struct sql
+ struct sql_statement *stmt = *_stmt;
+
+ *_stmt = NULL;
+- if (ctx->db->v.update_stmt != NULL)
+- ctx->db->v.update_stmt(ctx, stmt, affected_rows);
+- else
+- default_sql_update_stmt(ctx, stmt, affected_rows);
++ T_BEGIN {
++ if (ctx->db->v.update_stmt != NULL)
++ ctx->db->v.update_stmt(ctx, stmt, affected_rows);
++ else
++ default_sql_update_stmt(ctx, stmt, affected_rows);
++ } T_END;
+ }
+
+ void sql_db_set_state(struct sql_db *db, enum sql_db_state state)
--- /dev/null
+From aba5b8556952dbb47b9ed8b338d170cb73c80ce6 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 16 Apr 2026 13:27:42 +0200
+Subject: [PATCH 08/12] doveadm: Fix ubsan unsigned integer overflow error
+
+---
+ src/doveadm/doveadm-who.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/doveadm/doveadm-who.c b/src/doveadm/doveadm-who.c
+index 2fbef7bb94..ecdbb88bc7 100644
+--- a/src/doveadm/doveadm-who.c
++++ b/src/doveadm/doveadm-who.c
+@@ -49,10 +49,10 @@ static unsigned int who_user_hash(const struct who_user *user)
+ unsigned int hash = str_hash(user->service);
+
+ if (user->username[0] != '\0')
+- hash += str_hash(user->username);
++ hash ^= str_hash(user->username);
+ else {
+ who_user_ip(user, &ip);
+- hash += net_ip_hash(&ip);
++ hash ^= net_ip_hash(&ip);
+ }
+ return hash;
+ }
+--
+2.47.3
+
--- /dev/null
+From a444863fef605978c6f18e85db07f382d6f21d23 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Sun, 19 Apr 2026 18:15:49 +0300
+Subject: [PATCH 08/14] lib-mail: Make istream-header-filter's per-header size
+ limit configurable
+
+Until now the internal message_header_parser_ctx used the default
+MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE (10 MB) cap. In practice that cap
+only bounded hdr->full_value; per-chunk hdr->value was delivered in full
+regardless. A subsequent change to message_parse_header_next() will
+clamp hdr->value cumulatively as well, and at that point the 10 MB
+default would silently truncate data for callers like mbox-save that
+stream raw header bytes to storage.
+
+Default the filter to SIZE_MAX (unlimited) to preserve the effective
+behavior and add i_stream_header_filter_set_max_header_block_size() so
+callers that genuinely want a cap (index_mail_get_header_stream()) can
+opt in.
+
+Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
+---
+ src/lib-mail/istream-header-filter.c | 20 ++++++++++++++++++++
+ src/lib-mail/istream-header-filter.h | 8 ++++++++
+ 2 files changed, 28 insertions(+)
+
+Index: trixie/src/lib-mail/istream-header-filter.c
+===================================================================
+--- trixie.orig/src/lib-mail/istream-header-filter.c
++++ trixie/src/lib-mail/istream-header-filter.c
+@@ -29,6 +29,7 @@ struct header_filter_istream {
+ buffer_t *hdr_buf;
+ int snapshot_pending_refcount;
+ struct message_size header_size;
++ size_t max_header_block_size;
+ uoff_t skip_count;
+ uoff_t last_lf_offset;
+
+@@ -194,6 +195,8 @@ static ssize_t read_header(struct header
+ mstream->hdr_ctx =
+ message_parse_header_init(mstream->istream.parent,
+ NULL, 0);
++ message_parse_header_set_limit(mstream->hdr_ctx,
++ mstream->max_header_block_size);
+ }
+
+ /* remove skipped data from hdr_buf */
+@@ -709,6 +712,9 @@ i_stream_create_header_filter(struct ist
+ mstream->pool = pool_alloconly_create(MEMPOOL_GROWING
+ "header filter stream", 256);
+ mstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
++ /* Default to no per-header block size limit. Callers that want one
++ call i_stream_header_filter_set_max_header_block_size(). */
++ mstream->max_header_block_size = SIZE_MAX;
+
+ mstream->headers = headers_count == 0 ? NULL :
+ p_new(mstream->pool, const char *, headers_count);
+@@ -762,3 +768,17 @@ void i_stream_header_filter_add(struct h
+ buffer_append(input->hdr_buf, data, size);
+ input->headers_edited = TRUE;
+ }
++
++void i_stream_header_filter_set_max_header_block_size(
++ struct istream *input, size_t max_header_block_size)
++{
++ struct header_filter_istream *mstream =
++ container_of(input->real_stream, struct header_filter_istream,
++ istream);
++
++ mstream->max_header_block_size = max_header_block_size;
++ if (mstream->hdr_ctx != NULL) {
++ message_parse_header_set_limit(mstream->hdr_ctx,
++ max_header_block_size);
++ }
++}
+Index: trixie/src/lib-mail/istream-header-filter.h
+===================================================================
+--- trixie.orig/src/lib-mail/istream-header-filter.h
++++ trixie/src/lib-mail/istream-header-filter.h
+@@ -49,4 +49,12 @@ i_stream_create_header_filter(struct ist
+ void i_stream_header_filter_add(struct header_filter_istream *input,
+ const void *data, size_t size);
+
++/* Set a per-header-block maximum size for this filter stream. Data beyond
++ this limit in any single header is dropped from the filter's output and
++ from values delivered to the filter callback. The default is SIZE_MAX
++ (unlimited). Use MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE from
++ message-header-parser.h for the standard cap. */
++void i_stream_header_filter_set_max_header_block_size(
++ struct istream *input, size_t max_header_block_size);
++
+ #endif
--- /dev/null
+From 01c95a5503ec53c40f12d40de0b508838f6b041d Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Mon, 25 May 2026 14:54:03 +0000
+Subject: [PATCH] lib-sieve: Reduce CPU limit check interval in :contains and
+ :matches matchers
+
+Reduce the interval to 64 so worst-case work between checks is ~64 MiB.
+cpu_limit_exceeded() is a signal-counter comparison with no syscall, so
+the higher call frequency has no measurable cost.
+---
+ src/lib-sieve/mcht-contains.c | 2 +-
+ src/lib-sieve/mcht-matches.c | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+Index: trixie/pigeonhole/src/lib-sieve/mcht-contains.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/mcht-contains.c
++++ trixie/pigeonhole/src/lib-sieve/mcht-contains.c
+@@ -45,7 +45,7 @@ const struct sieve_match_type_def contai
+ * configured sieve_max_cpu_time (which is otherwise only checked between
+ * bytecode operations).
+ */
+-#define SIEVE_CONTAINS_CPU_CHECK_INTERVAL 4096
++#define SIEVE_CONTAINS_CPU_CHECK_INTERVAL 64
+
+ static int mcht_contains_match_key
+ (struct sieve_match_context *mctx, const char *val, size_t val_size,
+Index: trixie/pigeonhole/src/lib-sieve/mcht-matches.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve/mcht-matches.c
++++ trixie/pigeonhole/src/lib-sieve/mcht-matches.c
+@@ -53,7 +53,7 @@ const struct sieve_match_type_def matche
+ * configured sieve_max_cpu_time. Returns 1 on match, 0 on exhaustion, or -1
+ * when the CPU time limit was exceeded (mctx->exec_status is set).
+ */
+-#define SIEVE_MATCHES_CPU_CHECK_INTERVAL 4096
++#define SIEVE_MATCHES_CPU_CHECK_INTERVAL 64
+
+ static int
+ _string_find(struct sieve_match_context *mctx,
--- /dev/null
+From f5b9e0c2310b6fa744cf098dea9c03f88a985898 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 29 Sep 2025 14:09:42 +0300
+Subject: [PATCH 09/12] config: Assume dovecot_config_version=0.0.0 is the same
+ as the latest version
+
+It's used for git builds.
+---
+ src/config/config-parser.c | 16 +++++++++++++++-
+ src/config/config-parser.h | 3 +++
+ src/config/doveconf.c | 10 +++++++++-
+ 3 files changed, 27 insertions(+), 2 deletions(-)
+
+diff --git a/src/config/config-parser.c b/src/config/config-parser.c
+index b2883538ae..7e9db599f4 100644
+--- a/src/config/config-parser.c
++++ b/src/config/config-parser.c
+@@ -2604,6 +2604,17 @@ static bool config_version_find(const char *version, const char **error_r)
+ return TRUE;
+ }
+
++static bool
++dovecot_config_version_equals(struct config_parser_context *ctx, const char *value)
++{
++ if (strcmp(ctx->dovecot_config_version, value) == 0)
++ return TRUE;
++ if (strcmp(ctx->dovecot_config_version, CONFIG_VERSION_MAX) == 0 &&
++ strcmp(value, CONFIG_VERSION_GIT) == 0)
++ return TRUE;
++ return FALSE;
++}
++
+ static bool config_parser_get_version(struct config_parser_context *ctx,
+ const struct config_line *line)
+ {
+@@ -2616,7 +2627,7 @@ static bool config_parser_get_version(struct config_parser_context *ctx,
+ if (strcmp(line->key, "dovecot_config_version") == 0) {
+ if (ctx->dovecot_config_version == NULL)
+ ;
+- else if (strcmp(ctx->dovecot_config_version, line->value) != 0) {
++ else if (!dovecot_config_version_equals(ctx, line->value)) {
+ ctx->error = "dovecot_config_version value can't be changed once set";
+ return TRUE;
+ } else {
+@@ -2636,6 +2647,9 @@ static bool config_parser_get_version(struct config_parser_context *ctx,
+ else if (!config_version_find(line->value, &error)) {
+ ctx->error = p_strdup_printf(ctx->pool,
+ "Invalid dovecot_config_version: %s", error);
++ } else if (strcmp(line->value, CONFIG_VERSION_GIT) == 0) {
++ /* git build - this is the same as the latest version. */
++ ctx->dovecot_config_version = CONFIG_VERSION_MAX;
+ } else {
+ ctx->dovecot_config_version = p_strdup(ctx->pool, line->value);
+ }
+diff --git a/src/config/config-parser.h b/src/config/config-parser.h
+index 949aac100b..f9d70f1be8 100644
+--- a/src/config/config-parser.h
++++ b/src/config/config-parser.h
+@@ -5,6 +5,9 @@
+
+ #define CONFIG_MODULE_DIR MODULEDIR"/settings"
+
++#define CONFIG_VERSION_GIT "0.0.0"
++#define CONFIG_VERSION_MAX "9999.9999.9999"
++
+ /* change_counter used for default settings created internally */
+ #define CONFIG_PARSER_CHANGE_DEFAULTS 1
+ /* change_counter used for settings changed by configuration file */
+diff --git a/src/config/doveconf.c b/src/config/doveconf.c
+index 17dafbf006..825d84545c 100644
+--- a/src/config/doveconf.c
++++ b/src/config/doveconf.c
+@@ -1297,7 +1297,15 @@ int main(int argc, char *argv[])
+ printf("# %u default setting changes since version %s\n",
+ count, version);
+ }
+- printf("dovecot_config_version = %s\n", version);
++ if (strcmp(version, CONFIG_VERSION_MAX) != 0)
++ printf("dovecot_config_version = %s\n", version);
++ else {
++ /* GIT version was changed to MAX for easier
++ comparisons internally. However, output it
++ back as the original GIT version. */
++ printf("dovecot_config_version = %s\n",
++ CONFIG_VERSION_GIT);
++ }
+ }
+ if (!config_path_specified)
+ check_wrong_config(config_path);
+--
+2.47.3
+
--- /dev/null
+From 6802995e1302718a81bc1e39bdd43d8296421d11 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Sun, 19 Apr 2026 18:28:16 +0300
+Subject: [PATCH 09/14] lib-storage: Cap per-header size in
+ index_mail_get_header_stream()
+
+Apply MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE (10 MB) to the header-filter
+istream used for populating the header cache. A pathological single
+header (for example a To: with millions of addresses) otherwise grows
+mail->header_data and the cache write buffer in lockstep with the raw
+header size, which can push the imap process over vsz_limit on FETCH
+ENVELOPE / BODYSTRUCTURE.
+
+On its own this change does not yet bound hdr->value delivery; that
+requires the upcoming change to message_parse_header_next() to clamp
+per-chunk value_len cumulatively. Setting the limit here now lets that
+follow-up take effect without further touching this file.
+
+Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
+---
+ src/lib-storage/index/index-mail-headers.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/src/lib-storage/index/index-mail-headers.c b/src/lib-storage/index/index-mail-headers.c
+index 18c922239d..a9f4d55c0d 100644
+--- a/src/lib-storage/index/index-mail-headers.c
++++ b/src/lib-storage/index/index-mail-headers.c
+@@ -992,6 +992,11 @@ int index_mail_get_header_stream(struct mail *_mail,
+ HEADER_FILTER_HIDE_BODY,
+ headers->name, headers->count,
+ header_cache_callback, mail);
++ /* Cap per-header data so a single pathological header cannot exhaust
++ memory in mail->header_data / the filter's buffer. */
++ i_stream_header_filter_set_max_header_block_size(
++ mail->data.filter_stream,
++ MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE);
+ *stream_r = mail->data.filter_stream;
+ return 0;
+ }
+--
+2.47.3
+
--- /dev/null
+From 64ab59e17533a7eb63cbd4cd8cce81a39b32b2b1 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 20 Apr 2026 00:16:14 +0300
+Subject: [PATCH 10/14] lib-mail: Clamp hdr->value cumulatively against
+ header_block_max_size
+
+Until now header_block_max_size only bounded hdr->full_value via
+value_buf. Continued chunks returned to the caller via hdr->value were
+left at the raw chunk size, so a caller that consumed hdr->value per
+chunk without ever requesting use_full_value (e.g. the header-cache
+path in index_mail_parse_header()) could accumulate the full raw header
+size. A pathological To: with millions of addresses could grow
+mail->header_data and the cache write buffer to tens of megabytes each,
+driving the imap process over vsz_limit on FETCH ENVELOPE.
+
+Reinterpret header_block_total_size as the running sum of line_value_size
+across all chunks of all headers, and clamp each new chunk against the
+remaining header_block_max_size budget. Propagate the clamped size to
+line->value_len in the two continued-line branches that previously left
+it untouched. value_buf is bounded implicitly since every append uses
+line_value_size. The up-front per-chunk clamp
+(line->value_len = MIN(value_len, max_size)) is now subsumed by the
+cumulative clamp and has been removed.
+
+Update the truncation tests that were documenting the old
+"value_len stays at raw chunk size" behavior.
+
+Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
+---
+ src/lib-mail/message-header-parser.c | 16 +++++++++++-----
+ src/lib-mail/test-message-header-parser.c | 16 ++++++++++++----
+ 2 files changed, 23 insertions(+), 9 deletions(-)
+
+diff --git a/src/lib-mail/message-header-parser.c b/src/lib-mail/message-header-parser.c
+index b240a47884..3026a35f29 100644
+--- a/src/lib-mail/message-header-parser.c
++++ b/src/lib-mail/message-header-parser.c
+@@ -96,7 +96,6 @@ int message_parse_header_next(struct message_header_parser_ctx *ctx,
+ /* new header line */
+ line->name_offset = ctx->input->v_offset;
+ colon_pos = UINT_MAX;
+- ctx->header_block_total_size += ctx->value_buf->used;
+ buffer_set_used_size(ctx->value_buf, 0);
+ }
+
+@@ -362,12 +361,17 @@ int message_parse_header_next(struct message_header_parser_ctx *ctx,
+ }
+ }
+
+- line->value_len = I_MIN(line->value_len, ctx->header_block_max_size);
+ size_t line_value_size = line->value_len;
+- size_t header_total_used = ctx->header_block_total_size + ctx->value_buf->used;
+- size_t line_available = ctx->header_block_max_size <= header_total_used ? 0 :
+- ctx->header_block_max_size - header_total_used;
++ /* Clamp line_value_size against the remaining header_block_max_size
++ budget. header_block_total_size is the running sum of
++ line_value_size across all chunks of all headers; value_buf growth
++ is bounded implicitly because every append to value_buf uses
++ line_value_size. */
++ size_t line_available =
++ ctx->header_block_max_size <= ctx->header_block_total_size ? 0 :
++ ctx->header_block_max_size - ctx->header_block_total_size;
+ line_value_size = I_MIN(line_value_size, line_available);
++ ctx->header_block_total_size += line_value_size;
+
+ if (!line->continued) {
+ /* first header line. make a copy of the line since we can't
+@@ -397,10 +401,12 @@ int message_parse_header_next(struct message_header_parser_ctx *ctx,
+
+ line->full_value = ctx->value_buf->data;
+ line->full_value_len = ctx->value_buf->used;
++ line->value_len = line_value_size;
+ } else {
+ /* we didn't want full_value, and this is a continued line. */
+ line->full_value = NULL;
+ line->full_value_len = 0;
++ line->value_len = line_value_size;
+ }
+
+ /* always reset it */
+diff --git a/src/lib-mail/test-message-header-parser.c b/src/lib-mail/test-message-header-parser.c
+index 5395c54ed6..f2c9184e66 100644
+--- a/src/lib-mail/test-message-header-parser.c
++++ b/src/lib-mail/test-message-header-parser.c
+@@ -494,11 +494,14 @@ static void test_message_header_truncation_clean_oneline(void)
+ struct message_header_parser_ctx *parser = message_parse_header_init(input, NULL, MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE);
+ message_parse_header_set_limit(parser, 96);
+
++ /* hdr->value is now also clamped cumulatively against the same
++ header_block_max_size budget as full_value, so later chunks of a
++ multiline header shrink or disappear once the budget is used up. */
+ assert_parse_line( 1, "header1", "this is short", "this is short");
+ assert_parse_line( 2, "header2", "this is multiline", "this is multiline");
+ assert_parse_line( 3, "header2", " and long 343638404244464850525456586062", "this is multiline and long 343638404244464850525456586062");
+- assert_parse_line( 4, "header2", " 64666870727476788082848688909294969800", "this is multiline and long 343638404244464850525456586062 6466687072747678808284868");
+- assert_parse_line( 5, "header2", " 02040608101214161820222426283032343638", "this is multiline and long 343638404244464850525456586062 6466687072747678808284868");
++ assert_parse_line( 4, "header2", " 6466687072747678808284868", "this is multiline and long 343638404244464850525456586062 6466687072747678808284868");
++ assert_parse_line( 5, "header2", "", "this is multiline and long 343638404244464850525456586062 6466687072747678808284868");
+ assert_parse_line( 6, "header3", "", "");
+ test_assert(message_parse_header_next(parser, &hdr) > 0 && hdr->eoh);
+
+@@ -515,11 +518,16 @@ static void test_message_header_truncation_flag0(void)
+ struct message_header_parser_ctx *parser = message_parse_header_init(input, NULL, 0);
+ message_parse_header_set_limit(parser, 96);
+
++ /* hdr->value is clamped cumulatively against header_block_max_size.
++ The \n that NO flags inserts between continuations goes into
++ value_buf but is not added to header_block_total_size, so line 4's
++ value hits the same 26-byte cap as in CLEAN_ONELINE and
++ full_value_len is one byte larger than the nominal limit. */
+ assert_parse_line( 1, "header1", "this is short", "this is short");
+ assert_parse_line( 2, "header2", "this is multiline", "this is multiline");
+ assert_parse_line( 3, "header2", " and long 343638404244464850525456586062", "this is multiline\n and long 343638404244464850525456586062");
+- assert_parse_line( 4, "header2", " 64666870727476788082848688909294969800", "this is multiline\n and long 343638404244464850525456586062\n 646668707274767880828486");
+- assert_parse_line( 5, "header2", " 02040608101214161820222426283032343638", "this is multiline\n and long 343638404244464850525456586062\n 646668707274767880828486");
++ assert_parse_line( 4, "header2", " 6466687072747678808284868", "this is multiline\n and long 343638404244464850525456586062\n 6466687072747678808284868");
++ assert_parse_line( 5, "header2", "", "this is multiline\n and long 343638404244464850525456586062\n 6466687072747678808284868");
+ assert_parse_line( 6, "header3", "", "");
+ test_assert(message_parse_header_next(parser, &hdr) > 0 && hdr->eoh);
+
+--
+2.47.3
+
--- /dev/null
+From 9d9efd5ea41e0e3183e1e53d4326f40b65dd9ef8 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 1 May 2026 16:56:21 +0000
+Subject: [PATCH 10/12] lib-master: Move config_version_find() from
+ src/config/config-parser.c
+
+Rename to dovecot_config_version_find() and expose it via
+master-service-settings.h so non-config callers (e.g. the upcoming
+dovecot_storage_version validation in master_service_settings_check())
+can share the same supported-versions list. No behaviour change.
+---
+ src/config/config-parser.c | 31 ++++++-------------
+ src/lib-master/master-service-settings.c | 38 ++++++++++++++++++++++++
+ src/lib-master/master-service-settings.h | 5 ++++
+ 3 files changed, 52 insertions(+), 22 deletions(-)
+
+Index: trixie/src/config/config-parser.c
+===================================================================
+--- trixie.orig/src/config/config-parser.c
++++ trixie/src/config/config-parser.c
+@@ -2580,30 +2580,6 @@ config_parser_check_warnings(struct conf
+ hash_table_insert(ctx->seen_settings, path, first_pos);
+ }
+
+-static bool config_version_find(const char *version, const char **error_r)
+-{
+- const char *const supported_versions[] = {
+-#ifdef DOVECOT_PRO_EDITION
+- "3.1.0",
+-#else
+- "2.4.0",
+-#endif
+- NULL
+- };
+- /* FIXME: implement full version checking later */
+- if (!str_array_find(supported_versions, version) &&
+- strcmp(DOVECOT_CONFIG_VERSION, version) != 0) {
+- *error_r = t_strdup_printf(
+- "Currently supported versions are: %s%s",
+- t_strarray_join(supported_versions, " "),
+- str_array_find(supported_versions,
+- DOVECOT_CONFIG_VERSION) ? "" :
+- t_strdup_printf(" %s", DOVECOT_CONFIG_VERSION));
+- return FALSE;
+- }
+- return TRUE;
+-}
+-
+ static bool
+ dovecot_config_version_equals(struct config_parser_context *ctx, const char *value)
+ {
+@@ -2644,7 +2620,7 @@ static bool config_parser_get_version(st
+
+ if (line->type != CONFIG_LINE_TYPE_KEYVALUE)
+ ctx->error = "Invalid dovecot_config_version: value is not a string";
+- else if (!config_version_find(line->value, &error)) {
++ else if (!dovecot_config_version_find(line->value, &error)) {
+ ctx->error = p_strdup_printf(ctx->pool,
+ "Invalid dovecot_config_version: %s", error);
+ } else if (strcmp(line->value, CONFIG_VERSION_GIT) == 0) {
+Index: trixie/src/lib-master/master-service-settings.c
+===================================================================
+--- trixie.orig/src/lib-master/master-service-settings.c
++++ trixie/src/lib-master/master-service-settings.c
+@@ -146,6 +146,44 @@ master_service_set_process_shutdown_filt
+ master_service_set_process_shutdown_filter(master_service, filter);
+ }
+
++/* List of dovecot_config_versions accepted at parse / startup time.
++ dovecot_storage_version accepts these as well, plus an extra legacy set
++ defined in storage_version_check(). */
++static const char *const dovecot_config_supported_versions[] = {
++#ifdef DOVECOT_PRO_EDITION
++ "3.1.0",
++ "3.1.1",
++ "3.1.2",
++ "3.1.3",
++ "3.1.4",
++ "3.1.5",
++ "3.2.0",
++#else
++ "2.4.0",
++ "2.4.1",
++ "2.4.2",
++ "2.4.3",
++ "2.4.4",
++#endif
++ NULL
++};
++
++bool dovecot_config_version_find(const char *version, const char **error_r)
++{
++ /* FIXME: implement full version checking later */
++ if (!str_array_find(dovecot_config_supported_versions, version) &&
++ strcmp(DOVECOT_CONFIG_VERSION, version) != 0) {
++ *error_r = t_strdup_printf(
++ "Currently supported versions are: %s%s",
++ t_strarray_join(dovecot_config_supported_versions, " "),
++ str_array_find(dovecot_config_supported_versions,
++ DOVECOT_CONFIG_VERSION) ? "" :
++ t_strdup_printf(" %s", DOVECOT_CONFIG_VERSION));
++ return FALSE;
++ }
++ return TRUE;
++}
++
+ static bool storage_version_check(const char *version, const char **error_r)
+ {
+ #define STORAGE_MIN_VERSION "2.3.0"
+Index: trixie/src/lib-master/master-service-settings.h
+===================================================================
+--- trixie.orig/src/lib-master/master-service-settings.h
++++ trixie/src/lib-master/master-service-settings.h
+@@ -95,4 +95,9 @@ master_service_get_service_settings(stru
+ const char *
+ master_service_get_import_environment_keyvals(struct master_service *service);
+
++/* Returns TRUE if version is in the list of dovecot_config_version values
++ accepted by this build. Sets *error_r to a descriptive list of supported
++ versions on FALSE. */
++bool dovecot_config_version_find(const char *version, const char **error_r);
++
+ #endif
--- /dev/null
+From 0e7ba48c290b251b2f264192dd559b72d48a8005 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Sun, 5 Oct 2025 18:49:13 +0300
+Subject: [PATCH 11/12] lib, lib-master: Move version_*() to lib/version.[ch]
+
+---
+ src/anvil/anvil-connection.c | 1 +
+ src/config/config-connection.c | 1 +
+ src/config/config-parser.c | 3 +-
+ src/config/old-set-parser.c | 2 +-
+ src/doveadm/client-connection-tcp.c | 2 +-
+ src/doveadm/doveadm-penalty.c | 2 +-
+ src/doveadm/doveadm-stats.c | 2 +-
+ src/doveadm/doveadm-util.c | 1 +
+ src/doveadm/doveadm-who.c | 2 +-
+ src/doveadm/dsync/dsync-ibc-stream.c | 2 +-
+ src/imap-urlauth-login/imap-urlauth-login.c | 2 +-
+ src/imap/imap-client-hibernate.c | 2 +-
+ src/lib-doveadm/doveadm-client.c | 2 +-
+ src/lib-master/Makefile.am | 5 --
+ src/lib-master/master-service-settings.c | 1 +
+ src/lib-master/master-service.c | 85 ------------------
+ src/lib-master/master-service.h | 15 ----
+ src/lib/Makefile.am | 3 +
+ src/lib/test-lib.inc | 1 +
+ .../test-version.c} | 16 ++--
+ src/lib/version.c | 90 +++++++++++++++++++
+ src/lib/version.h | 19 ++++
+ src/util/script-login.c | 1 +
+ src/util/script.c | 1 +
+ 24 files changed, 136 insertions(+), 125 deletions(-)
+ rename src/{lib-master/test-master-service.c => lib/test-version.c} (83%)
+ create mode 100644 src/lib/version.c
+ create mode 100644 src/lib/version.h
+
+diff --git a/src/anvil/anvil-connection.c b/src/anvil/anvil-connection.c
+index 13c57bf3e7..9cd44563ed 100644
+--- a/src/anvil/anvil-connection.c
++++ b/src/anvil/anvil-connection.c
+@@ -11,6 +11,7 @@
+ #include "connection.h"
+ #include "str.h"
+ #include "strescape.h"
++#include "version.h"
+ #include "master-service.h"
+ #include "master-interface.h"
+ #include "connect-limit.h"
+diff --git a/src/config/config-connection.c b/src/config/config-connection.c
+index d9e0a13207..265c702601 100644
+--- a/src/config/config-connection.c
++++ b/src/config/config-connection.c
+@@ -7,6 +7,7 @@
+ #include "ostream.h"
+ #include "ostream-unix.h"
+ #include "strescape.h"
++#include "version.h"
+ #include "settings-parser.h"
+ #include "master-service.h"
+ #include "master-service-settings.h"
+diff --git a/src/config/config-parser.c b/src/config/config-parser.c
+index 55e124e88b..07dca715f9 100644
+--- a/src/config/config-parser.c
++++ b/src/config/config-parser.c
+@@ -9,9 +9,9 @@
+ #include "strescape.h"
+ #include "istream.h"
+ #include "module-dir.h"
++#include "version.h"
+ #include "settings.h"
+ #include "service-settings.h"
+-#include "master-service.h"
+ #include "master-service-settings.h"
+ #include "all-settings.h"
+ #include "old-set-parser.h"
+@@ -22,6 +22,7 @@
+
+ #include "default-settings-import.h"
+
++#include <stdio.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+ #include <time.h>
+diff --git a/src/config/old-set-parser.c b/src/config/old-set-parser.c
+index 4ae8506dc7..ee2cb513c9 100644
+--- a/src/config/old-set-parser.c
++++ b/src/config/old-set-parser.c
+@@ -2,7 +2,7 @@
+
+ #include "lib.h"
+ #include "array.h"
+-#include "master-service.h"
++#include "version.h"
+ #include "settings-history.h"
+ #include "config-parser-private.h"
+ #include "old-set-parser.h"
+diff --git a/src/doveadm/client-connection-tcp.c b/src/doveadm/client-connection-tcp.c
+index 9299596f29..936bb03f01 100644
+--- a/src/doveadm/client-connection-tcp.c
++++ b/src/doveadm/client-connection-tcp.c
+@@ -8,9 +8,9 @@
+ #include "istream.h"
+ #include "ostream.h"
+ #include "strescape.h"
++#include "version.h"
+ #include "iostream-ssl.h"
+ #include "ostream-multiplex.h"
+-#include "master-service.h"
+ #include "master-service-ssl.h"
+ #include "mail-storage-service.h"
+ #include "doveadm-util.h"
+diff --git a/src/doveadm/doveadm-penalty.c b/src/doveadm/doveadm-penalty.c
+index 8a098b3453..71a997e2e8 100644
+--- a/src/doveadm/doveadm-penalty.c
++++ b/src/doveadm/doveadm-penalty.c
+@@ -7,7 +7,7 @@
+ #include "hash.h"
+ #include "strescape.h"
+ #include "time-util.h"
+-#include "master-service.h"
++#include "version.h"
+ #include "doveadm.h"
+ #include "doveadm-print.h"
+
+diff --git a/src/doveadm/doveadm-stats.c b/src/doveadm/doveadm-stats.c
+index bd7552211a..87aef26a89 100644
+--- a/src/doveadm/doveadm-stats.c
++++ b/src/doveadm/doveadm-stats.c
+@@ -6,7 +6,7 @@
+ #include "str.h"
+ #include "strescape.h"
+ #include "write-full.h"
+-#include "master-service.h"
++#include "version.h"
+ #include "doveadm.h"
+ #include "doveadm-print.h"
+ #include "stats-settings.h"
+diff --git a/src/doveadm/doveadm-util.c b/src/doveadm/doveadm-util.c
+index c21dbee6c0..4bea2437cc 100644
+--- a/src/doveadm/doveadm-util.c
++++ b/src/doveadm/doveadm-util.c
+@@ -7,6 +7,7 @@
+ #include "ostream.h"
+ #include "net.h"
+ #include "time-util.h"
++#include "version.h"
+ #include "master-service.h"
+ #include "module-dir.h"
+ #include "doveadm-settings.h"
+diff --git a/src/doveadm/doveadm-who.c b/src/doveadm/doveadm-who.c
+index ecdbb88bc7..29172afba0 100644
+--- a/src/doveadm/doveadm-who.c
++++ b/src/doveadm/doveadm-who.c
+@@ -8,7 +8,7 @@
+ #include "hash.h"
+ #include "str.h"
+ #include "strescape.h"
+-#include "master-service.h"
++#include "version.h"
+ #include "doveadm.h"
+ #include "doveadm-print.h"
+ #include "doveadm-who.h"
+diff --git a/src/doveadm/dsync/dsync-ibc-stream.c b/src/doveadm/dsync/dsync-ibc-stream.c
+index 957c569cee..472d1fc517 100644
+--- a/src/doveadm/dsync/dsync-ibc-stream.c
++++ b/src/doveadm/dsync/dsync-ibc-stream.c
+@@ -10,7 +10,7 @@
+ #include "ostream.h"
+ #include "str.h"
+ #include "strescape.h"
+-#include "master-service.h"
++#include "version.h"
+ #include "mail-cache.h"
+ #include "mail-storage-private.h"
+ #include "dsync-serializer.h"
+diff --git a/src/imap-urlauth-login/imap-urlauth-login.c b/src/imap-urlauth-login/imap-urlauth-login.c
+index 23a76f6c81..68b4aab159 100644
+--- a/src/imap-urlauth-login/imap-urlauth-login.c
++++ b/src/imap-urlauth-login/imap-urlauth-login.c
+@@ -8,7 +8,7 @@
+ #include "ioloop.h"
+ #include "istream.h"
+ #include "ostream.h"
+-#include "master-service.h"
++#include "version.h"
+ #include "auth-client.h"
+ #include "client-common.h"
+
+diff --git a/src/imap/imap-client-hibernate.c b/src/imap/imap-client-hibernate.c
+index ade5d250ea..067b984cc0 100644
+--- a/src/imap/imap-client-hibernate.c
++++ b/src/imap/imap-client-hibernate.c
+@@ -9,7 +9,7 @@
+ #include "base64.h"
+ #include "str.h"
+ #include "strescape.h"
+-#include "master-service.h"
++#include "version.h"
+ #include "compression.h"
+ #include "mailbox-watch.h"
+ #include "imap-state.h"
+diff --git a/src/lib-doveadm/doveadm-client.c b/src/lib-doveadm/doveadm-client.c
+index 47b09f677f..6d570169ec 100644
+--- a/src/lib-doveadm/doveadm-client.c
++++ b/src/lib-doveadm/doveadm-client.c
+@@ -10,8 +10,8 @@
+ #include "ostream-dot.h"
+ #include "str.h"
+ #include "strescape.h"
++#include "version.h"
+ #include "iostream-ssl.h"
+-#include "master-service.h"
+ #include "doveadm-protocol.h"
+ #include "doveadm-client.h"
+ #include "dns-lookup.h"
+diff --git a/src/lib-master/Makefile.am b/src/lib-master/Makefile.am
+index d6fe251e43..8939f759de 100644
+--- a/src/lib-master/Makefile.am
++++ b/src/lib-master/Makefile.am
+@@ -47,7 +47,6 @@ pkginc_lib_HEADERS = $(headers)
+
+ test_programs = \
+ test-event-stats \
+- test-master-service \
+ test-master-service-settings
+
+ noinst_PROGRAMS = $(test_programs)
+@@ -71,10 +70,6 @@ test_event_stats_SOURCES = test-event-stats.c
+ test_event_stats_LDADD = $(test_libs)
+ test_event_stats_DEPENDENCIES = $(test_deps)
+
+-test_master_service_SOURCES = test-master-service.c
+-test_master_service_LDADD = $(test_libs)
+-test_master_service_DEPENDENCIES = $(test_deps)
+-
+ test_master_service_settings_SOURCES = test-master-service-settings.c
+ test_master_service_settings_LDADD = $(test_libs)
+ test_master_service_settings_DEPENDENCIES = $(test_deps)
+diff --git a/src/lib-master/master-service-settings.c b/src/lib-master/master-service-settings.c
+index 71438c0c4f..4194c19fab 100644
+--- a/src/lib-master/master-service-settings.c
++++ b/src/lib-master/master-service-settings.c
+@@ -11,6 +11,7 @@
+ #include "eacces-error.h"
+ #include "env-util.h"
+ #include "execv-const.h"
++#include "version.h"
+ #include "settings.h"
+ #include "stats-client.h"
+ #include "master-service-private.h"
+diff --git a/src/lib-master/master-service.c b/src/lib-master/master-service.c
+index a18cb39dbe..8f2ccacee1 100644
+--- a/src/lib-master/master-service.c
++++ b/src/lib-master/master-service.c
+@@ -2035,91 +2035,6 @@ void master_status_update(struct master_service *service)
+ master_status_send(service, important_update);
+ }
+
+-bool version_string_verify(const char *line, const char *service_name,
+- unsigned int major_version)
+-{
+- unsigned int minor_version;
+-
+- return version_string_verify_full(line, service_name,
+- major_version, &minor_version);
+-}
+-
+-bool version_string_verify_full(const char *line, const char *service_name,
+- unsigned int major_version,
+- unsigned int *minor_version_r)
+-{
+- size_t service_name_len = strlen(service_name);
+- bool ret;
+-
+- if (!str_begins(line, "VERSION\t", &line))
+- return FALSE;
+-
+- if (strncmp(line, service_name, service_name_len) != 0 ||
+- line[service_name_len] != '\t')
+- return FALSE;
+- line += service_name_len + 1;
+-
+- T_BEGIN {
+- const char *p = strchr(line, '\t');
+-
+- if (p == NULL)
+- ret = FALSE;
+- else {
+- ret = str_uint_equals(t_strdup_until(line, p),
+- major_version);
+- if (str_to_uint(p+1, minor_version_r) < 0)
+- ret = FALSE;
+- }
+- } T_END;
+- return ret;
+-}
+-
+-int version_cmp(const char *version1, const char *version2)
+-{
+- unsigned int v1, v2;
+-
+- do {
+- if (str_parse_uint(version1, &v1, &version1) < 0)
+- i_unreached();
+- if (str_parse_uint(version2, &v2, &version2) < 0)
+- i_unreached();
+- if (*version1 == '.')
+- version1++;
+- else
+- i_assert(*version1 == '\0');
+- if (*version2 == '.')
+- version2++;
+- else
+- i_assert(*version2 == '\0');
+-
+- if (v1 < v2)
+- return -1;
+- if (v1 > v2)
+- return 1;
+- } while (*version1 != '\0' && *version2 != '\0');
+-
+- if (*version1 != '\0')
+- return 1;
+- if (*version2 != '\0')
+- return -1;
+- return 0;
+-}
+-
+-bool version_is_valid(const char *version)
+-{
+- unsigned int i;
+-
+- for (i = 0; version[i] != '\0'; i++) {
+- if (version[i] == '.') {
+- if (i == 0 || version[i-1] == '.' ||
+- version[i+1] == '\0')
+- return FALSE;
+- } else if (version[i] < '0' || version[i] > '9')
+- return FALSE;
+- }
+- return i > 0;
+-}
+-
+ void master_service_set_process_shutdown_filter(struct master_service *service,
+ struct event_filter *filter)
+ {
+diff --git a/src/lib-master/master-service.h b/src/lib-master/master-service.h
+index 1dca3a6403..6c13ddaca7 100644
+--- a/src/lib-master/master-service.h
++++ b/src/lib-master/master-service.h
+@@ -308,21 +308,6 @@ void master_service_deinit(struct master_service **service);
+ */
+ void master_service_deinit_forked(struct master_service **_service);
+
+-/* Returns TRUE if line contains compatible service name and major version.
+- The line is expected to be in format:
+- VERSION <tab> service_name <tab> major version <tab> minor version */
+-bool version_string_verify(const char *line, const char *service_name,
+- unsigned int major_version);
+-/* Same as version_string_verify(), but return the minor version. */
+-bool version_string_verify_full(const char *line, const char *service_name,
+- unsigned int major_version,
+- unsigned int *minor_version_r);
+-/* Compare number[.number[...]] style version numbers. Assert-crash if the
+- version strings are invalid. */
+-int version_cmp(const char *version1, const char *version2);
+-/* Returns TRUE if version string is a valid number[.number[...]] string. */
+-bool version_is_valid(const char *version);
+-
+ /* Sets process shutdown filter */
+ void master_service_set_process_shutdown_filter(struct master_service *service,
+ struct event_filter *filter);
+diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
+index eb9b9ddda3..45a4f1160e 100644
+--- a/src/lib/Makefile.am
++++ b/src/lib/Makefile.am
+@@ -197,6 +197,7 @@ liblib_la_SOURCES = \
+ uri-util.c \
+ utc-offset.c \
+ utc-mktime.c \
++ version.c \
+ wildcard-match.c \
+ write-full.c
+
+@@ -358,6 +359,7 @@ headers = \
+ uri-util.h \
+ utc-offset.h \
+ utc-mktime.h \
++ version.h \
+ wildcard-match.h \
+ write-full.h
+
+@@ -464,6 +466,7 @@ test_lib_SOURCES = \
+ test-unichar.c \
+ test-utc-mktime.c \
+ test-uri.c \
++ test-version.c \
+ test-wildcard-match.c
+
+ test_headers = \
+diff --git a/src/lib/test-lib.inc b/src/lib/test-lib.inc
+index 2fceca2f9b..52259e4b96 100644
+--- a/src/lib/test-lib.inc
++++ b/src/lib/test-lib.inc
+@@ -109,4 +109,5 @@ TEST(test_time_util)
+ TEST(test_unichar)
+ TEST(test_uri)
+ TEST(test_utc_mktime)
++TEST(test_version)
+ TEST(test_wildcard_match)
+diff --git a/src/lib-master/test-master-service.c b/src/lib/test-version.c
+similarity index 83%
+rename from src/lib-master/test-master-service.c
+rename to src/lib/test-version.c
+index f30b250628..e938333f08 100644
+--- a/src/lib-master/test-master-service.c
++++ b/src/lib/test-version.c
+@@ -1,7 +1,7 @@
+-/* Copyright (c) 2023 Dovecot authors, see the included COPYING file */
++/* Copyright (c) 2023-2025 Dovecot authors, see the included COPYING file */
+
+-#include "lib.h"
+-#include "master-service.h"
++#include "test-lib.h"
++#include "version.h"
+ #include "test-common.h"
+
+ static void test_version_is_valid(void)
+@@ -56,12 +56,8 @@ static void test_version_cmp(void)
+ test_end();
+ }
+
+-int main(void)
++void test_version(void)
+ {
+- static void (*const test_functions[])(void) = {
+- test_version_is_valid,
+- test_version_cmp,
+- NULL
+- };
+- return test_run(test_functions);
++ test_version_is_valid();
++ test_version_cmp();
+ }
+diff --git a/src/lib/version.c b/src/lib/version.c
+new file mode 100644
+index 0000000000..f8bec420ab
+--- /dev/null
++++ b/src/lib/version.c
+@@ -0,0 +1,90 @@
++/* Copyright (c) 2005-2025 Dovecot authors, see the included COPYING file */
++
++#include "lib.h"
++#include "version.h"
++
++bool version_string_verify(const char *line, const char *service_name,
++ unsigned int major_version)
++{
++ unsigned int minor_version;
++
++ return version_string_verify_full(line, service_name,
++ major_version, &minor_version);
++}
++
++bool version_string_verify_full(const char *line, const char *service_name,
++ unsigned int major_version,
++ unsigned int *minor_version_r)
++{
++ size_t service_name_len = strlen(service_name);
++ bool ret;
++
++ if (!str_begins(line, "VERSION\t", &line))
++ return FALSE;
++
++ if (strncmp(line, service_name, service_name_len) != 0 ||
++ line[service_name_len] != '\t')
++ return FALSE;
++ line += service_name_len + 1;
++
++ T_BEGIN {
++ const char *p = strchr(line, '\t');
++
++ if (p == NULL)
++ ret = FALSE;
++ else {
++ ret = str_uint_equals(t_strdup_until(line, p),
++ major_version);
++ if (str_to_uint(p+1, minor_version_r) < 0)
++ ret = FALSE;
++ }
++ } T_END;
++ return ret;
++}
++
++int version_cmp(const char *version1, const char *version2)
++{
++ unsigned int v1, v2;
++
++ do {
++ if (str_parse_uint(version1, &v1, &version1) < 0)
++ i_unreached();
++ if (str_parse_uint(version2, &v2, &version2) < 0)
++ i_unreached();
++ if (*version1 == '.')
++ version1++;
++ else
++ i_assert(*version1 == '\0');
++ if (*version2 == '.')
++ version2++;
++ else
++ i_assert(*version2 == '\0');
++
++ if (v1 < v2)
++ return -1;
++ if (v1 > v2)
++ return 1;
++ } while (*version1 != '\0' && *version2 != '\0');
++
++ if (*version1 != '\0')
++ return 1;
++ if (*version2 != '\0')
++ return -1;
++ return 0;
++}
++
++bool version_is_valid(const char *version)
++{
++ unsigned int i;
++
++ for (i = 0; version[i] != '\0'; i++) {
++ if (version[i] == '.') {
++ if (i == 0 || version[i-1] == '.' ||
++ version[i+1] == '\0')
++ return FALSE;
++ } else if (version[i] < '0' || version[i] > '9')
++ return FALSE;
++ }
++ return i > 0;
++}
++
+diff --git a/src/lib/version.h b/src/lib/version.h
+new file mode 100644
+index 0000000000..f305f7f4e1
+--- /dev/null
++++ b/src/lib/version.h
+@@ -0,0 +1,19 @@
++#ifndef VERSION_H
++#define VERSION_H
++
++/* Returns TRUE if line contains compatible service name and major version.
++ The line is expected to be in format:
++ VERSION <tab> service_name <tab> major version <tab> minor version */
++bool version_string_verify(const char *line, const char *service_name,
++ unsigned int major_version);
++/* Same as version_string_verify(), but return the minor version. */
++bool version_string_verify_full(const char *line, const char *service_name,
++ unsigned int major_version,
++ unsigned int *minor_version_r);
++/* Compare number[.number[...]] style version numbers. Assert-crash if the
++ version strings are invalid. */
++int version_cmp(const char *version1, const char *version2);
++/* Returns TRUE if version string is a valid number[.number[...]] string. */
++bool version_is_valid(const char *version);
++
++#endif
+diff --git a/src/util/script-login.c b/src/util/script-login.c
+index ed356747dc..4df7f23174 100644
+--- a/src/util/script-login.c
++++ b/src/util/script-login.c
+@@ -7,6 +7,7 @@
+ #include "restrict-access.h"
+ #include "str.h"
+ #include "strescape.h"
++#include "version.h"
+ #include "settings-parser.h"
+ #include "mail-storage-service.h"
+ #include "master-interface.h"
+diff --git a/src/util/script.c b/src/util/script.c
+index 5838cdad31..391b87bc06 100644
+--- a/src/util/script.c
++++ b/src/util/script.c
+@@ -8,6 +8,7 @@
+ #include "execv-const.h"
+ #include "write-full.h"
+ #include "restrict-access.h"
++#include "version.h"
+ #include "master-interface.h"
+ #include "master-service.h"
+
+--
+2.47.3
+
--- /dev/null
+From 3e8ffe61ee5719c05e68d9be7f913dc3cd2a0e6c Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 17 Apr 2026 15:54:48 +0200
+Subject: [PATCH 11/14] lib-mail: Introduce struct message_part_data_limits and
+ thread it through parsers
+
+Add an empty struct message_part_data_limits and pass it by pointer through
+message_part_data_parse_from_header() and message_part_envelope_parse_from_header().
+
+No behaviour change: the struct has no fields yet and no limits are applied.
+Subsequent commits add the individual limit fields and enforcement.
+
+Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
+---
+ src/lib-imap/test-imap-bodystructure.c | 3 ++-
+ src/lib-imap/test-imap-envelope.c | 4 +++-
+ src/lib-mail/message-part-data.c | 5 ++++-
+ src/lib-mail/message-part-data.h | 7 +++++++
+ src/lib-mail/test-message-decoder.c | 16 ++++++++++++----
+ src/lib-mail/test-message-parser.c | 3 ++-
+ src/lib-mail/test-message-part-data.c | 3 ++-
+ src/lib-storage/index/index-mail-headers.c | 10 +++++++---
+ src/lib-storage/index/index-mail.c | 21 +++++++++++++++++----
+ src/lib-storage/index/index-mail.h | 2 ++
+ 10 files changed, 58 insertions(+), 16 deletions(-)
+
+diff --git a/src/lib-imap/test-imap-bodystructure.c b/src/lib-imap/test-imap-bodystructure.c
+index 40c8a8c6ec..abe35ac6f3 100644
+--- a/src/lib-imap/test-imap-bodystructure.c
++++ b/src/lib-imap/test-imap-bodystructure.c
+@@ -398,10 +398,11 @@ msg_parse(pool_t pool, const char *message, unsigned int max_nested_mime_parts,
+
+ input = i_stream_create_from_data(message, strlen(message));
+ parser = message_parser_init(pool, input, &parser_set);
++ struct message_part_data_limits limits = MESSAGE_PART_DATA_LIMITS_INIT;
+ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
+ if (parse_bodystructure) {
+ message_part_data_parse_from_header(pool, block.part,
+- block.hdr);
++ &limits, block.hdr);
+ }
+ }
+ test_assert(ret < 0);
+diff --git a/src/lib-imap/test-imap-envelope.c b/src/lib-imap/test-imap-envelope.c
+index c9b92b4be2..498c6ac9f4 100644
+--- a/src/lib-imap/test-imap-envelope.c
++++ b/src/lib-imap/test-imap-envelope.c
+@@ -132,9 +132,11 @@ msg_parse(pool_t pool, const char *message)
+
+ input = i_stream_create_from_data(message, strlen(message));
+ parser = message_parser_init(pool, input, &parser_set);
++ struct message_part_data_limits limits = MESSAGE_PART_DATA_LIMITS_INIT;
+ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
+ i_assert(block.part->parent == NULL);
+- message_part_envelope_parse_from_header(pool, &envlp, block.hdr);
++ message_part_envelope_parse_from_header(pool, &envlp, &limits,
++ block.hdr);
+ }
+ test_assert(ret < 0);
+
+diff --git a/src/lib-mail/message-part-data.c b/src/lib-mail/message-part-data.c
+index acd50e5210..c302fe568b 100644
+--- a/src/lib-mail/message-part-data.c
++++ b/src/lib-mail/message-part-data.c
+@@ -173,6 +173,7 @@ envelope_get_field(const char *name)
+
+ void message_part_envelope_parse_from_header(pool_t pool,
+ struct message_part_envelope **data,
++ struct message_part_data_limits *limits ATTR_UNUSED,
+ struct message_header_line *hdr)
+ {
+ struct message_part_envelope *d;
+@@ -480,6 +481,7 @@ parse_content_header(struct message_part_data *data,
+
+ void message_part_data_parse_from_header(pool_t pool,
+ struct message_part *part,
++ struct message_part_data_limits *limits,
+ struct message_header_line *hdr)
+ {
+ struct message_part_data *part_data;
+@@ -525,7 +527,8 @@ void message_part_data_parse_from_header(pool_t pool,
+
+ if (parent_rfc822) {
+ /* message/rfc822, we need the envelope */
+- message_part_envelope_parse_from_header(pool, &part_data->envelope, hdr);
++ message_part_envelope_parse_from_header(pool, &part_data->envelope,
++ limits, hdr);
+ }
+ }
+
+diff --git a/src/lib-mail/message-part-data.h b/src/lib-mail/message-part-data.h
+index 9513bfc2f0..1514f0daf2 100644
+--- a/src/lib-mail/message-part-data.h
++++ b/src/lib-mail/message-part-data.h
+@@ -8,6 +8,11 @@
+
+ struct message_header_line;
+
++struct message_part_data_limits {
++};
++
++#define MESSAGE_PART_DATA_LIMITS_INIT { }
++
+ struct message_part_param {
+ const char *name;
+ const char *value;
+@@ -91,11 +96,13 @@ bool message_part_is_attachment(struct message_part *part,
+ /* Update envelope data based from given header field */
+ void message_part_envelope_parse_from_header(pool_t pool,
+ struct message_part_envelope **_data,
++ struct message_part_data_limits *limits,
+ struct message_header_line *hdr);
+
+ /* Parse a single header. Note that this modifies part->context. */
+ void message_part_data_parse_from_header(pool_t pool,
+ struct message_part *part,
++ struct message_part_data_limits *limits,
+ struct message_header_line *hdr);
+
+ #endif
+diff --git a/src/lib-mail/test-message-decoder.c b/src/lib-mail/test-message-decoder.c
+index 83c267187b..ad212c8546 100644
+--- a/src/lib-mail/test-message-decoder.c
++++ b/src/lib-mail/test-message-decoder.c
+@@ -260,8 +260,10 @@ static void test_message_decoder_content_transfer_encoding(void)
+ parser = message_parser_init(pool, istream, &parser_set);
+ decoder = message_decoder_init(NULL, 0);
+
++ struct message_part_data_limits limits = MESSAGE_PART_DATA_LIMITS_INIT;
+ while ((ret = message_parser_parse_next_block(parser, &input)) > 0) {
+- message_part_data_parse_from_header(pool, input.part, input.hdr);
++ message_part_data_parse_from_header(pool, input.part, &limits,
++ input.hdr);
+ if (message_decoder_decode_next_block(decoder, &input, &output) &&
+ output.hdr == NULL && output.size > 0)
+ str_append_data(str_out, output.data, output.size);
+@@ -349,8 +351,10 @@ static void test_message_decoder_invalid_content_transfer_encoding(void)
+ parser = message_parser_init(pool, istream, &parser_set);
+ decoder = message_decoder_init(NULL, 0);
+
++ struct message_part_data_limits limits = MESSAGE_PART_DATA_LIMITS_INIT;
+ while ((ret = message_parser_parse_next_block(parser, &input)) > 0) {
+- message_part_data_parse_from_header(pool, input.part, input.hdr);
++ message_part_data_parse_from_header(pool, input.part, &limits,
++ input.hdr);
+ if (input.hdr != NULL &&
+ strcasecmp(input.hdr->name, "content-transfer-encoding") == 0) {
+ enum message_cte cte = message_decoder_parse_cte(input.hdr);
+@@ -480,8 +484,10 @@ UNICODE_REPLACEMENT_CHAR_UTF8;
+ parser = message_parser_init(pool, istream, &parser_set);
+ decoder = message_decoder_init(NULL, 0);
+
++ struct message_part_data_limits limits = MESSAGE_PART_DATA_LIMITS_INIT;
+ while ((ret = message_parser_parse_next_block(parser, &input)) > 0) {
+- message_part_data_parse_from_header(pool, input.part, input.hdr);
++ message_part_data_parse_from_header(pool, input.part, &limits,
++ input.hdr);
+ if (message_decoder_decode_next_block(decoder, &input, &output) &&
+ output.hdr == NULL && output.size > 0)
+ str_append_data(str_out, output.data, output.size);
+@@ -531,8 +537,10 @@ static void test_message_decoder_charset_mime_part_change(void)
+ parser = message_parser_init(pool, istream, &parser_set);
+ decoder = message_decoder_init(NULL, 0);
+
++ struct message_part_data_limits limits = MESSAGE_PART_DATA_LIMITS_INIT;
+ while ((ret = message_parser_parse_next_block(parser, &input)) > 0) {
+- message_part_data_parse_from_header(pool, input.part, input.hdr);
++ message_part_data_parse_from_header(pool, input.part, &limits,
++ input.hdr);
+ if (message_decoder_decode_next_block(decoder, &input, &output) &&
+ output.hdr == NULL && output.size > 0)
+ str_append_data(str_out, output.data, output.size);
+diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c
+index 3b94034c09..b66e0a664e 100644
+--- a/src/lib-mail/test-message-parser.c
++++ b/src/lib-mail/test-message-parser.c
+@@ -51,12 +51,13 @@ static int message_parse_stream(pool_t pool, struct istream *input,
+ struct message_parser_ctx *parser;
+ struct message_block block;
+
++ struct message_part_data_limits limits = MESSAGE_PART_DATA_LIMITS_INIT;
+ i_zero(&block);
+ parser = message_parser_init(pool, input, set);
+ while ((ret = message_parser_parse_next_block(parser, &block)) > 0)
+ if (parse_data)
+ message_part_data_parse_from_header(pool, block.part,
+- block.hdr);
++ &limits, block.hdr);
+ message_parser_deinit(&parser, parts_r);
+ test_assert(input->stream_errno == 0);
+ return ret;
+diff --git a/src/lib-mail/test-message-part-data.c b/src/lib-mail/test-message-part-data.c
+index 470d739b5e..63228d1b3d 100644
+--- a/src/lib-mail/test-message-part-data.c
++++ b/src/lib-mail/test-message-part-data.c
+@@ -14,12 +14,13 @@ static int message_parse_stream(pool_t pool, struct istream *input,
+ struct message_parser_ctx *parser;
+ struct message_block block;
+
++ struct message_part_data_limits limits = MESSAGE_PART_DATA_LIMITS_INIT;
+ i_zero(&block);
+ parser = message_parser_init(pool, input, set);
+ while ((ret = message_parser_parse_next_block(parser, &block)) > 0)
+ if (parse_data)
+ message_part_data_parse_from_header(pool, block.part,
+- block.hdr);
++ &limits, block.hdr);
+ message_parser_deinit(&parser, parts_r);
+ test_assert(input->stream_errno == 0);
+ return ret;
+diff --git a/src/lib-storage/index/index-mail-headers.c b/src/lib-storage/index/index-mail-headers.c
+index a9f4d55c0d..9d608acc61 100644
+--- a/src/lib-storage/index/index-mail-headers.c
++++ b/src/lib-storage/index/index-mail-headers.c
+@@ -308,12 +308,15 @@ void index_mail_parse_header(struct message_part *part,
+ if (data->save_bodystructure_header &&
+ !data->parsed_bodystructure_header) {
+ i_assert(part != NULL);
+- message_part_data_parse_from_header(mail->mail.data_pool, part, hdr);
++ message_part_data_parse_from_header(mail->mail.data_pool, part,
++ &mail->data.part_data_limits,
++ hdr);
+ }
+
+ if (data->save_envelope) {
+ message_part_envelope_parse_from_header(mail->mail.data_pool,
+- &data->envelope_data, hdr);
++ &data->envelope_data,
++ &mail->data.part_data_limits, hdr);
+
+ if (hdr == NULL)
+ index_mail_parse_finish_imap_envelope(mail);
+@@ -510,7 +513,8 @@ imap_envelope_parse_callback(struct message_header_line *hdr,
+ struct index_mail *mail)
+ {
+ message_part_envelope_parse_from_header(mail->mail.data_pool,
+- &mail->data.envelope_data, hdr);
++ &mail->data.envelope_data,
++ &mail->data.part_data_limits, hdr);
+
+ if (hdr == NULL)
+ index_mail_parse_finish_imap_envelope(mail);
+diff --git a/src/lib-storage/index/index-mail.c b/src/lib-storage/index/index-mail.c
+index 70060ba4f7..3911f6db1c 100644
+--- a/src/lib-storage/index/index-mail.c
++++ b/src/lib-storage/index/index-mail.c
+@@ -696,11 +696,16 @@ void index_mail_cache_pop3_data(struct mail *_mail,
+ &order, sizeof(order));
+ }
+
++struct parse_bodystructure_ctx {
++ pool_t pool;
++ struct message_part_data_limits *limits;
++};
++
+ static void parse_bodystructure_part_header(struct message_part *part,
+ struct message_header_line *hdr,
+- pool_t pool)
++ struct parse_bodystructure_ctx *ctx)
+ {
+- message_part_data_parse_from_header(pool, part, hdr);
++ message_part_data_parse_from_header(ctx->pool, part, ctx->limits, hdr);
+ }
+
+ static bool want_plain_bodystructure_cached(struct index_mail *mail)
+@@ -1317,9 +1322,13 @@ static int index_mail_parse_body(struct index_mail *mail,
+ /* bodystructure header is parsed, we want the body's mime
+ headers too */
+ i_assert(data->parsed_bodystructure_header);
++ struct parse_bodystructure_ctx bsctx = {
++ .pool = mail->mail.data_pool,
++ .limits = &mail->data.part_data_limits,
++ };
+ message_parser_parse_body(data->parser_ctx,
+ parse_bodystructure_part_header,
+- mail->mail.data_pool);
++ &bsctx);
+ } else {
+ message_parser_parse_body(data->parser_ctx,
+ *null_message_part_header_callback, NULL);
+@@ -1860,6 +1869,8 @@ static void index_mail_init_data(struct index_mail *mail)
+ data->received_date = (time_t)-1;
+ data->sent_date.time = (uint32_t)-1;
+ data->dont_cache_field_idx = UINT_MAX;
++ data->part_data_limits = (struct message_part_data_limits)
++ MESSAGE_PART_DATA_LIMITS_INIT;
+
+ data->wanted_fields = mail->mail.wanted_fields;
+ if (mail->mail.wanted_headers != NULL) {
+@@ -2321,7 +2332,9 @@ void index_mail_cache_parse_continue(struct mail *_mail)
+ mail->data.header_parsed = TRUE;
+ } else {
+ message_part_data_parse_from_header(mail->mail.data_pool,
+- block.part, block.hdr);
++ block.part,
++ &mail->data.part_data_limits,
++ block.hdr);
+ }
+ }
+ }
+diff --git a/src/lib-storage/index/index-mail.h b/src/lib-storage/index/index-mail.h
+index 9e2159898e..d1625b3f6e 100644
+--- a/src/lib-storage/index/index-mail.h
++++ b/src/lib-storage/index/index-mail.h
+@@ -2,6 +2,7 @@
+ #define INDEX_MAIL_H
+
+ #include "message-size.h"
++#include "message-part-data.h"
+ #include "mail-cache.h"
+ #include "mail-storage-private.h"
+
+@@ -84,6 +85,7 @@ struct index_mail_data {
+ const char *envelope, *body, *bodystructure, *guid, *filename;
+ const char *from_envelope, *body_snippet;
+ struct message_part_envelope *envelope_data;
++ struct message_part_data_limits part_data_limits;
+
+ uint32_t cache_flags;
+ uint64_t modseq, pvt_modseq;
+--
+2.47.3
+
--- /dev/null
+From ba5efe2541fa89e9cfe4e7fae4526e0f5dc18a9c Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 9 Apr 2025 14:11:42 +0300
+Subject: [PATCH 12/12] config: doveconf -dF - Add dovecot_storage_version
+
+---
+ src/config/config-parser.c | 14 ++++++++++++++
+ src/config/config-parser.h | 3 +++
+ src/config/doveconf.c | 2 ++
+ 3 files changed, 19 insertions(+)
+
+Index: trixie/src/config/config-parser.c
+===================================================================
+--- trixie.orig/src/config/config-parser.c
++++ trixie/src/config/config-parser.c
+@@ -2958,6 +2958,20 @@ int config_parse_file(const char *path,
+ hook_config_parser_begin(&ctx);
+ } T_END;
+
++ if ((flags & CONFIG_PARSE_FLAG_DEFAULT_VERSION) != 0) {
++ /* Use default settings. Set dovecot_storage_version to the
++ latest version, so it won't cause a failure.
++
++ When building from git we don't know the latest version, so
++ just use 9999. The version validity checks are disabled for
++ git builds, so this should work. */
++ const char *version = version_is_valid(DOVECOT_VERSION) ?
++ DOVECOT_VERSION : "9999";
++ if (config_apply_exact_line(&ctx, NULL, "dovecot_storage_version",
++ version) < 0)
++ i_panic("Couldn't set default dovecot_storage_version: %s", ctx.error);
++ }
++
+ internal.path = "Internal config_import";
+ ctx.cur_input->input = config_import == NULL ?
+ i_stream_create_from_data("", 0) :
+Index: trixie/src/config/config-parser.h
+===================================================================
+--- trixie.orig/src/config/config-parser.h
++++ trixie/src/config/config-parser.h
+@@ -40,6 +40,9 @@ enum config_parse_flags {
+ CONFIG_PARSE_FLAG_MERGE_DEFAULT_FILTERS = BIT(9),
+ /* Ignore unknown settings in the config file. */
+ CONFIG_PARSE_FLAG_IGNORE_UNKNOWN = BIT(10),
++ /* Executing "doveconf -dF" - add default dovecot_storage_version
++ setting. */
++ CONFIG_PARSE_FLAG_DEFAULT_VERSION = BIT(11),
+ };
+
+ /* Used to track changed settings for a setting_parser_info. Initially only
+Index: trixie/src/config/doveconf.c
+===================================================================
+--- trixie.orig/src/config/doveconf.c
++++ trixie/src/config/doveconf.c
+@@ -1234,6 +1234,8 @@ int main(int argc, char *argv[])
+ flags |= CONFIG_PARSE_FLAG_DELAY_ERRORS;
+ if (exec_args == NULL)
+ flags |= CONFIG_PARSE_FLAG_EXTERNAL_HOOKS;
++ else if (dump_defaults)
++ flags |= CONFIG_PARSE_FLAG_DEFAULT_VERSION;
+ T_BEGIN {
+ ret = config_parse_file(dump_defaults ? NULL : config_path,
+ flags, have_dump_filter ? &dump_filter : NULL,
--- /dev/null
+From f42717983640b9df13ce3214cec311102d9dcb6b Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 17 Apr 2026 15:55:48 +0200
+Subject: [PATCH 12/14] lib-mail: Limit total address count per message to 100
+ 000
+
+A message with millions of addresses across all its envelope headers can
+exhaust memory when parsed (each struct message_address is ~100 bytes
+regardless of whether the raw address is only a few bytes long).
+
+Add remaining_addresses to struct message_part_data_limits, initialised to
+MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES (100 000). Pass the remaining budget
+as max_addresses to message_address_parse_full() so parsing stops at the
+limit, then deduct the actual count parsed from the budget.
+
+Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
+---
+ src/lib-mail/message-part-data.c | 7 +++++--
+ src/lib-mail/message-part-data.h | 6 +++++-
+ 2 files changed, 10 insertions(+), 3 deletions(-)
+
+diff --git a/src/lib-mail/message-part-data.c b/src/lib-mail/message-part-data.c
+index c302fe568b..24e1a31964 100644
+--- a/src/lib-mail/message-part-data.c
++++ b/src/lib-mail/message-part-data.c
+@@ -173,7 +173,7 @@ envelope_get_field(const char *name)
+
+ void message_part_envelope_parse_from_header(pool_t pool,
+ struct message_part_envelope **data,
+- struct message_part_data_limits *limits ATTR_UNUSED,
++ struct message_part_data_limits *limits,
+ struct message_header_line *hdr)
+ {
+ struct message_part_envelope *d;
+@@ -238,9 +238,12 @@ void message_part_envelope_parse_from_header(pool_t pool,
+ if (addr_p != NULL) {
+ message_address_parse_full(pool, hdr->full_value,
+ hdr->full_value_len,
+- UINT_MAX,
++ limits->remaining_addresses,
+ MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING,
+ &new_addr);
++ i_assert(new_addr.count <= limits->remaining_addresses);
++ limits->remaining_addresses -= new_addr.count;
++
+ /* Merge multiple headers the same as if they were comma
+ separated in a single line. This is better from security
+ point of view, because attacker could intentionally write
+diff --git a/src/lib-mail/message-part-data.h b/src/lib-mail/message-part-data.h
+index 1514f0daf2..af09ea715e 100644
+--- a/src/lib-mail/message-part-data.h
++++ b/src/lib-mail/message-part-data.h
+@@ -8,10 +8,14 @@
+
+ struct message_header_line;
+
++#define MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES 100000
++
+ struct message_part_data_limits {
++ unsigned int remaining_addresses;
+ };
+
+-#define MESSAGE_PART_DATA_LIMITS_INIT { }
++#define MESSAGE_PART_DATA_LIMITS_INIT \
++ { MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES }
+
+ struct message_part_param {
+ const char *name;
+--
+2.47.3
+
--- /dev/null
+From 5292d2277474680902437ec2a23c684041cdab61 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 17 Apr 2026 15:56:37 +0200
+Subject: [PATCH 13/14] lib-mail: Limit total Content-Language tag count per
+ message to 100 000
+
+A multipart message with many MIME parts each containing many language tags
+can exhaust memory: the per-part RFC 2231 parser had no cumulative limit.
+
+Add remaining_language_tags to struct message_part_data_limits, initialised
+to MESSAGE_PART_DATA_MAX_TOTAL_LANGUAGE_TAGS (100 000). Break out of the
+tag-parsing loop in parse_content_language() once the budget reaches zero.
+
+Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
+---
+ src/lib-mail/message-part-data.c | 15 ++++++++++-----
+ src/lib-mail/message-part-data.h | 5 ++++-
+ 2 files changed, 14 insertions(+), 6 deletions(-)
+
+diff --git a/src/lib-mail/message-part-data.c b/src/lib-mail/message-part-data.c
+index 24e1a31964..2eed994e45 100644
+--- a/src/lib-mail/message-part-data.c
++++ b/src/lib-mail/message-part-data.c
+@@ -380,7 +380,8 @@ parse_content_disposition(struct message_part_data *data,
+
+ static void
+ parse_content_language(struct message_part_data *data,
+- pool_t pool, const unsigned char *value, size_t value_len)
++ pool_t pool, const unsigned char *value, size_t value_len,
++ struct message_part_data_limits *limits)
+ {
+ struct rfc822_parser_context parser;
+ ARRAY_TYPE(const_string) langs;
+@@ -398,8 +399,11 @@ parse_content_language(struct message_part_data *data,
+
+ rfc822_skip_lwsp(&parser);
+ while (rfc822_parse_atom(&parser, str) >= 0) {
+- const char *lang = p_strdup(pool, str_c(str));
++ if (limits->remaining_language_tags == 0)
++ break;
++ limits->remaining_language_tags--;
+
++ const char *lang = p_strdup(pool, str_c(str));
+ array_push_back(&langs, &lang);
+ str_truncate(str, 0);
+
+@@ -419,7 +423,8 @@ parse_content_language(struct message_part_data *data,
+
+ static void
+ parse_content_header(struct message_part_data *data,
+- pool_t pool, struct message_header_line *hdr)
++ pool_t pool, struct message_header_line *hdr,
++ struct message_part_data_limits *limits)
+ {
+ const char *name = hdr->name + strlen("Content-");
+
+@@ -459,7 +464,7 @@ parse_content_header(struct message_part_data *data,
+ if (strcasecmp(name, "Language") == 0 &&
+ data->content_language == NULL) {
+ parse_content_language(data, pool,
+- hdr->full_value, hdr->full_value_len);
++ hdr->full_value, hdr->full_value_len, limits);
+ } else if (strcasecmp(name, "Location") == 0 &&
+ data->content_location == NULL) {
+ data->content_location =
+@@ -524,7 +529,7 @@ void message_part_data_parse_from_header(pool_t pool,
+
+ if (str_begins_icase_with(hdr->name, "Content-")) {
+ T_BEGIN {
+- parse_content_header(part_data, pool, hdr);
++ parse_content_header(part_data, pool, hdr, limits);
+ } T_END;
+ }
+
+diff --git a/src/lib-mail/message-part-data.h b/src/lib-mail/message-part-data.h
+index af09ea715e..7f1f9c3e1f 100644
+--- a/src/lib-mail/message-part-data.h
++++ b/src/lib-mail/message-part-data.h
+@@ -9,13 +9,16 @@
+ struct message_header_line;
+
+ #define MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES 100000
++#define MESSAGE_PART_DATA_MAX_TOTAL_LANGUAGE_TAGS 100000
+
+ struct message_part_data_limits {
+ unsigned int remaining_addresses;
++ unsigned int remaining_language_tags;
+ };
+
+ #define MESSAGE_PART_DATA_LIMITS_INIT \
+- { MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES }
++ { MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES, \
++ MESSAGE_PART_DATA_MAX_TOTAL_LANGUAGE_TAGS }
+
+ struct message_part_param {
+ const char *name;
+--
+2.47.3
+
--- /dev/null
+From 6a71e37c3edae89cd446a1bb8b1f7137b5a25754 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 17 Apr 2026 15:57:24 +0200
+Subject: [PATCH 14/14] lib-mail: Limit total MIME parameter count per message
+ to 200 000
+
+Content-Type and Content-Disposition parameters are both parsed through
+parse_mime_parameters(). A message with many MIME parts each having many
+parameters can accumulate millions of struct message_part_param entries.
+
+Add remaining_mime_params to struct message_part_data_limits (combined
+budget for both Content-Type and Content-Disposition), initialised to
+MESSAGE_PART_DATA_MAX_TOTAL_MIME_PARAMS (200 000). Cap params_count to the
+remaining budget before allocating and deduct accordingly.
+
+Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
+---
+ src/lib-mail/message-part-data.c | 21 ++++++++++++++-------
+ src/lib-mail/message-part-data.h | 6 +++++-
+ 2 files changed, 19 insertions(+), 8 deletions(-)
+
+diff --git a/src/lib-mail/message-part-data.c b/src/lib-mail/message-part-data.c
+index 2eed994e45..e0a3676aae 100644
+--- a/src/lib-mail/message-part-data.c
++++ b/src/lib-mail/message-part-data.c
+@@ -263,7 +263,8 @@ void message_part_envelope_parse_from_header(pool_t pool,
+ static void
+ parse_mime_parameters(struct rfc822_parser_context *parser,
+ pool_t pool, const struct message_part_param **params_r,
+- unsigned int *params_count_r)
++ unsigned int *params_count_r,
++ struct message_part_data_limits *limits)
+ {
+ const char *const *results;
+ struct message_part_param *params;
+@@ -275,6 +276,10 @@ parse_mime_parameters(struct rfc822_parser_context *parser,
+ i_assert((params_count % 2) == 0);
+ params_count /= 2;
+
++ if (params_count > limits->remaining_mime_params)
++ params_count = limits->remaining_mime_params;
++ limits->remaining_mime_params -= params_count;
++
+ if (params_count > 0) {
+ params = p_new(pool, struct message_part_param, params_count);
+ for (i = 0; i < params_count; i++) {
+@@ -289,7 +294,8 @@ parse_mime_parameters(struct rfc822_parser_context *parser,
+
+ static void
+ parse_content_type(struct message_part_data *data,
+- pool_t pool, struct message_header_line *hdr)
++ pool_t pool, struct message_header_line *hdr,
++ struct message_part_data_limits *limits)
+ {
+ struct rfc822_parser_context parser;
+ string_t *str;
+@@ -332,7 +338,7 @@ parse_content_type(struct message_part_data *data,
+
+ parse_mime_parameters(&parser, pool,
+ &data->content_type_params,
+- &data->content_type_params_count);
++ &data->content_type_params_count, limits);
+ rfc822_parser_deinit(&parser);
+ }
+
+@@ -357,7 +363,8 @@ parse_content_transfer_encoding(struct message_part_data *data,
+
+ static void
+ parse_content_disposition(struct message_part_data *data,
+- pool_t pool, struct message_header_line *hdr)
++ pool_t pool, struct message_header_line *hdr,
++ struct message_part_data_limits *limits)
+ {
+ struct rfc822_parser_context parser;
+ string_t *str;
+@@ -374,7 +381,7 @@ parse_content_disposition(struct message_part_data *data,
+
+ parse_mime_parameters(&parser, pool,
+ &data->content_disposition_params,
+- &data->content_disposition_params_count);
++ &data->content_disposition_params_count, limits);
+ rfc822_parser_deinit(&parser);
+ }
+
+@@ -453,7 +460,7 @@ parse_content_header(struct message_part_data *data,
+ case 't':
+ case 'T':
+ if (strcasecmp(name, "Type") == 0 && data->content_type == NULL)
+- parse_content_type(data, pool, hdr);
++ parse_content_type(data, pool, hdr, limits);
+ else if (strcasecmp(name, "Transfer-Encoding") == 0 &&
+ data->content_transfer_encoding == NULL)
+ parse_content_transfer_encoding(data, pool, hdr);
+@@ -482,7 +489,7 @@ parse_content_header(struct message_part_data *data,
+ hdr->full_value_len);
+ else if (strcasecmp(name, "Disposition") == 0 &&
+ data->content_disposition_params == NULL)
+- parse_content_disposition(data, pool, hdr);
++ parse_content_disposition(data, pool, hdr, limits);
+ break;
+ }
+ }
+diff --git a/src/lib-mail/message-part-data.h b/src/lib-mail/message-part-data.h
+index 7f1f9c3e1f..f439926c90 100644
+--- a/src/lib-mail/message-part-data.h
++++ b/src/lib-mail/message-part-data.h
+@@ -10,15 +10,19 @@ struct message_header_line;
+
+ #define MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES 100000
+ #define MESSAGE_PART_DATA_MAX_TOTAL_LANGUAGE_TAGS 100000
++/* Combined limit for Content-Type + Content-Disposition parameters */
++#define MESSAGE_PART_DATA_MAX_TOTAL_MIME_PARAMS 200000
+
+ struct message_part_data_limits {
+ unsigned int remaining_addresses;
+ unsigned int remaining_language_tags;
++ unsigned int remaining_mime_params;
+ };
+
+ #define MESSAGE_PART_DATA_LIMITS_INIT \
+ { MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES, \
+- MESSAGE_PART_DATA_MAX_TOTAL_LANGUAGE_TAGS }
++ MESSAGE_PART_DATA_MAX_TOTAL_LANGUAGE_TAGS, \
++ MESSAGE_PART_DATA_MAX_TOTAL_MIME_PARAMS }
+
+ struct message_part_param {
+ const char *name;
+--
+2.47.3
+
--- /dev/null
+From dae1a1b84115b2a6146afd9a51e17137264caa0d Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 4 Nov 2025 11:34:30 +0200
+Subject: [PATCH 01/24] auth: Don't disconnect auth client when invalid base64
+ SASL input is received
+
+The base64 input comes from untrusted client. It shouldn't cause the auth
+client to disconnect, which causes other concurrent logins to be aborted.
+
+Broken by 1486c30e191ff079bfa78e7950173bb33d8073d9
+---
+ src/auth/auth-request-handler.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+Index: dovecot/src/auth/auth-request-handler.c
+===================================================================
+--- dovecot.orig/src/auth/auth-request-handler.c
++++ dovecot/src/auth/auth-request-handler.c
+@@ -715,6 +715,9 @@ int auth_request_handler_auth_begin(stru
+ auth_request_handler_auth_fail_code(handler, request,
+ AUTH_CLIENT_FAIL_CODE_INVALID_BASE64,
+ "Invalid base64 data in initial response");
++ /* The base64 input came from untrusted client. It's
++ an expected auth failure, so don't disconnect the
++ auth client. */
+ return 1;
+ }
+ initial_resp_data =
+@@ -787,7 +790,10 @@ int auth_request_handler_auth_continue(s
+ auth_request_handler_auth_fail_code(handler, request,
+ AUTH_CLIENT_FAIL_CODE_INVALID_BASE64,
+ "Invalid base64 data in continued response");
+- return -1;
++ /* The base64 input came from untrusted client. It's
++ an expected auth failure, so don't disconnect the
++ auth client. */
++ return 1;
+ }
+ }
+
--- /dev/null
+From 089edc7750160bf224011c015347db1bdea435e3 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Thu, 8 Jan 2026 08:51:59 +0200
+Subject: [PATCH 02/24] fts: Remove decode2text.sh
+
+The script is flawed and not fit for production use, should
+recommend writing your own script, or using Apache Tika.
+---
+ src/plugins/fts/Makefile.am | 3 -
+ src/plugins/fts/decode2text.sh | 105 ---------------------------------
+ 2 files changed, 108 deletions(-)
+ delete mode 100755 src/plugins/fts/decode2text.sh
+
+Index: dovecot/src/plugins/fts/Makefile.am
+===================================================================
+--- dovecot.orig/src/plugins/fts/Makefile.am
++++ dovecot/src/plugins/fts/Makefile.am
+@@ -65,9 +65,6 @@ xml2text_CPPFLAGS = $(AM_CPPFLAGS) $(BIN
+ xml2text_LDADD = $(LIBDOVECOT) $(BINARY_LDFLAGS)
+ xml2text_DEPENDENCIES = $(module_LTLIBRARIES) $(LIBDOVECOT_DEPS)
+
+-pkglibexec_SCRIPTS = decode2text.sh
+-EXTRA_DIST = $(pkglibexec_SCRIPTS)
+-
+ doveadm_module_LTLIBRARIES = \
+ lib20_doveadm_fts_plugin.la
+
+Index: dovecot/src/plugins/fts/decode2text.sh
+===================================================================
+--- dovecot.orig/src/plugins/fts/decode2text.sh
++++ /dev/null
+@@ -1,105 +0,0 @@
+-#!/bin/sh
+-
+-# Example attachment decoder script. The attachment comes from stdin, and
+-# the script is expected to output UTF-8 data to stdout. (If the output isn't
+-# UTF-8, everything except valid UTF-8 sequences are dropped from it.)
+-
+-# The attachment decoding is enabled by setting:
+-#
+-# plugin {
+-# fts_decoder = decode2text
+-# }
+-# service decode2text {
+-# executable = script /usr/local/libexec/dovecot/decode2text.sh
+-# user = dovecot
+-# unix_listener decode2text {
+-# mode = 0666
+-# }
+-# }
+-
+-libexec_dir=`dirname $0`
+-content_type=$1
+-
+-# The second parameter is the format's filename extension, which is used when
+-# found from a filename of application/octet-stream. You can also add more
+-# extensions by giving more parameters.
+-formats='application/pdf pdf
+-application/x-pdf pdf
+-application/msword doc
+-application/mspowerpoint ppt
+-application/vnd.ms-powerpoint ppt
+-application/ms-excel xls
+-application/x-msexcel xls
+-application/vnd.ms-excel xls
+-application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
+-application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx
+-application/vnd.openxmlformats-officedocument.presentationml.presentation pptx
+-application/vnd.oasis.opendocument.text odt
+-application/vnd.oasis.opendocument.spreadsheet ods
+-application/vnd.oasis.opendocument.presentation odp
+-'
+-
+-if [ "$content_type" = "" ]; then
+- echo "$formats"
+- exit 0
+-fi
+-
+-fmt=`echo "$formats" | grep -w "^$content_type" | cut -d ' ' -f 2`
+-if [ "$fmt" = "" ]; then
+- echo "Content-Type: $content_type not supported" >&2
+- exit 1
+-fi
+-
+-# most decoders can't handle stdin directly, so write the attachment
+-# to a temp file
+-path=`mktemp`
+-trap "rm -f $path" 0 1 2 3 14 15
+-cat > $path
+-
+-xmlunzip() {
+- name=$1
+-
+- tempdir=`mktemp -d`
+- if [ "$tempdir" = "" ]; then
+- exit 1
+- fi
+- trap "rm -rf $path $tempdir" 0 1 2 3 14 15
+- cd $tempdir || exit 1
+- unzip -q "$path" 2>/dev/null || exit 0
+- find . -name "$name" -print0 | xargs -0 cat |
+- $libexec_dir/xml2text
+-}
+-
+-wait_timeout() {
+- childpid=$!
+- trap "kill -9 $childpid; rm -f $path" 1 2 3 14 15
+- wait $childpid
+-}
+-
+-LANG=en_US.UTF-8
+-export LANG
+-if [ $fmt = "pdf" ]; then
+- /usr/bin/pdftotext $path - 2>/dev/null&
+- wait_timeout 2>/dev/null
+-elif [ $fmt = "doc" ]; then
+- (/usr/bin/catdoc $path; true) 2>/dev/null&
+- wait_timeout 2>/dev/null
+-elif [ $fmt = "ppt" ]; then
+- (/usr/bin/catppt $path; true) 2>/dev/null&
+- wait_timeout 2>/dev/null
+-elif [ $fmt = "xls" ]; then
+- (/usr/bin/xls2csv $path; true) 2>/dev/null&
+- wait_timeout 2>/dev/null
+-elif [ $fmt = "odt" -o $fmt = "ods" -o $fmt = "odp" ]; then
+- xmlunzip "content.xml"
+-elif [ $fmt = "docx" ]; then
+- xmlunzip "document.xml"
+-elif [ $fmt = "xlsx" ]; then
+- xmlunzip "sharedStrings.xml"
+-elif [ $fmt = "pptx" ]; then
+- xmlunzip "slide*.xml"
+-else
+- echo "Buggy decoder script: $fmt not handled" >&2
+- exit 1
+-fi
+-exit 0
--- /dev/null
+From efb68fac3a9d2d04d38c4ab14dd570cf0c23923c Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 22 Dec 2025 22:25:04 +0200
+Subject: [PATCH] managesieve-login: Fix crash when command didn't finish on
+ the first call
+
+---
+ src/managesieve-login/client.c | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+Index: dovecot/pigeonhole/src/managesieve-login/client.c
+===================================================================
+--- dovecot.orig/pigeonhole/src/managesieve-login/client.c
++++ dovecot/pigeonhole/src/managesieve-login/client.c
+@@ -336,10 +336,12 @@ static bool managesieve_client_input_nex
+ if (args[0].type != MANAGESIEVE_ARG_EOL)
+ ret = -1;
+ }
+- }
+- if (ret > 0) {
++ if (ret > 0)
++ ret = msieve_client->cmd->func(msieve_client, args);
++ } else {
++ /* Continue unfinished command */
+ i_assert(msieve_client->cmd != NULL);
+- ret = msieve_client->cmd->func(msieve_client, args);
++ ret = msieve_client->cmd->func(msieve_client, NULL);
+ }
+
+ if (ret != 0)
--- /dev/null
+From 466023fc20b05eb6a79d85b0d5efaa45a81ca48d Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 25 Feb 2026 09:33:25 +0200
+Subject: [PATCH 04/24] auth: Make struct settings_get_params params const
+
+---
+ src/auth/passdb-sql.c | 2 +-
+ src/auth/userdb-sql.c | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/auth/passdb-sql.c b/src/auth/passdb-sql.c
+index db1990dc47..f3682d4926 100644
+--- a/src/auth/passdb-sql.c
++++ b/src/auth/passdb-sql.c
+@@ -182,7 +182,7 @@ static void sql_lookup_pass(struct passdb_sql_request *sql_request)
+ const struct passdb_sql_settings *set;
+ const char *error;
+
+- struct settings_get_params params = {
++ const struct settings_get_params params = {
+ .escape_func = passdb_sql_escape,
+ .escape_context = module->db,
+ };
+diff --git a/src/auth/userdb-sql.c b/src/auth/userdb-sql.c
+index f97fe5e316..349f61c49e 100644
+--- a/src/auth/userdb-sql.c
++++ b/src/auth/userdb-sql.c
+@@ -128,7 +128,7 @@ static void userdb_sql_lookup(struct auth_request *auth_request,
+ const struct userdb_sql_settings *set;
+ const char *error;
+
+- struct settings_get_params params = {
++ const struct settings_get_params params = {
+ .escape_func = userdb_sql_escape,
+ .escape_context = module->db,
+ };
+--
+2.39.5
+
--- /dev/null
+From 531c0801f0431eadf91383dab25aa9a8ede0d22f Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 20 Feb 2026 18:37:38 +0200
+Subject: [PATCH 05/24] auth: passdb/userdb ldap - Fix escaping ldap filter,
+ base and bind_userdn
+
+Broken by c2ccdab8d09dec65753ee42366f48d53d7f47cfd
+---
+ src/auth/passdb-ldap.c | 23 +++++++++++++++++------
+ src/auth/userdb-ldap.c | 23 +++++++++++++++++------
+ 2 files changed, 34 insertions(+), 12 deletions(-)
+
+Index: dovecot/src/auth/passdb-ldap.c
+===================================================================
+--- dovecot.orig/src/auth/passdb-ldap.c
++++ dovecot/src/auth/passdb-ldap.c
+@@ -376,9 +376,12 @@ ldap_verify_plain(struct auth_request *r
+ return;
+ }
+
++ const struct settings_get_params params = {
++ .escape_func = ldap_escape,
++ };
+ const struct ldap_pre_settings *ldap_pre = NULL;
+- if (settings_get(event, &ldap_pre_setting_parser_info, 0,
+- &ldap_pre, &error) < 0 ||
++ if (settings_get_params(event, &ldap_pre_setting_parser_info,
++ ¶ms, &ldap_pre, &error) < 0 ||
+ ldap_pre_settings_post_check(ldap_pre, DB_LDAP_LOOKUP_TYPE_PASSDB,
+ &error) < 0) {
+ e_error(event, "%s", error);
+@@ -414,10 +417,13 @@ static void ldap_lookup_credentials(stru
+ auth_request_ref(request);
+ ldap_request->request.ldap.auth_request = request;
+
++ const struct settings_get_params params = {
++ .escape_func = ldap_escape,
++ };
+ const char *error;
+ const struct ldap_pre_settings *ldap_pre = NULL;
+- if (settings_get(event, &ldap_pre_setting_parser_info, 0,
+- &ldap_pre, &error) < 0 ||
++ if (settings_get_params(event, &ldap_pre_setting_parser_info, ¶ms,
++ &ldap_pre, &error) < 0 ||
+ ldap_pre_settings_post_check(ldap_pre, DB_LDAP_LOOKUP_TYPE_PASSDB,
+ &error) < 0) {
+ e_error(event, "%s", error);
+@@ -446,8 +452,13 @@ static int passdb_ldap_preinit(pool_t po
+ if (settings_get(event, &auth_passdb_post_setting_parser_info,
+ RAW_SETTINGS, &auth_post, error_r) < 0)
+ goto failed;
+- if (settings_get(event, &ldap_pre_setting_parser_info,
+- RAW_SETTINGS, &ldap_pre, error_r) < 0)
++
++ const struct settings_get_params params = {
++ .escape_func = ldap_escape,
++ .flags = RAW_SETTINGS,
++ };
++ if (settings_get_params(event, &ldap_pre_setting_parser_info,
++ ¶ms, &ldap_pre, error_r) < 0)
+ goto failed;
+
+ module = p_new(pool, struct ldap_passdb_module, 1);
+Index: dovecot/src/auth/userdb-ldap.c
+===================================================================
+--- dovecot.orig/src/auth/userdb-ldap.c
++++ dovecot/src/auth/userdb-ldap.c
+@@ -122,9 +122,12 @@ static void userdb_ldap_lookup(struct au
+ struct userdb_ldap_request *request;
+ const char *error;
+
++ const struct settings_get_params params = {
++ .escape_func = ldap_escape,
++ };
+ const struct ldap_pre_settings *ldap_pre = NULL;
+- if (settings_get(event, &ldap_pre_setting_parser_info, 0,
+- &ldap_pre, &error) < 0 ||
++ if (settings_get_params(event, &ldap_pre_setting_parser_info, ¶ms,
++ &ldap_pre, &error) < 0 ||
+ ldap_pre_settings_post_check(ldap_pre, DB_LDAP_LOOKUP_TYPE_USERDB,
+ &error) < 0) {
+ e_error(event, "%s", error);
+@@ -258,9 +261,12 @@ userdb_ldap_iterate_init(struct auth_req
+ request = &ctx->request;
+ request->ctx = ctx;
+
++ const struct settings_get_params params = {
++ .escape_func = ldap_escape,
++ };
+ const struct ldap_pre_settings *ldap_pre = NULL;
+- if (settings_get(event, &ldap_pre_setting_parser_info, 0,
+- &ldap_pre, &error) < 0 ||
++ if (settings_get_params(event, &ldap_pre_setting_parser_info, ¶ms,
++ &ldap_pre, &error) < 0 ||
+ ldap_pre_settings_post_check(ldap_pre, DB_LDAP_LOOKUP_TYPE_ITERATE,
+ &error) < 0) {
+ e_error(event, "%s", error);
+@@ -331,8 +337,13 @@ static int userdb_ldap_preinit(pool_t po
+ if (settings_get(event, &ldap_post_setting_parser_info,
+ RAW_SETTINGS, &ldap_post, error_r) < 0)
+ goto failed;
+- if (settings_get(event, &ldap_pre_setting_parser_info,
+- RAW_SETTINGS, &ldap_pre, error_r) < 0)
++
++ const struct settings_get_params params = {
++ .escape_func = ldap_escape,
++ .flags = RAW_SETTINGS,
++ };
++ if (settings_get_params(event, &ldap_pre_setting_parser_info,
++ ¶ms, &ldap_pre, error_r) < 0)
+ goto failed;
+
+ module = p_new(pool, struct ldap_userdb_module, 1);
--- /dev/null
+From 926057d36ce5d3ec6833c6a6d3e3b732e0a39453 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 23 Feb 2026 13:37:09 +0200
+Subject: [PATCH 06/24] lib-settings: settings_get_params() - Fix using
+ provided escape_func
+
+This fixes auth-sql and auth-ldap to actually do escaping.
+---
+ src/lib-settings/settings.c | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+Index: dovecot/src/lib-settings/settings.c
+===================================================================
+--- dovecot.orig/src/lib-settings/settings.c
++++ dovecot/src/lib-settings/settings.c
+@@ -1540,8 +1540,14 @@ settings_var_expand_init(struct settings
+ ctx->var_params.tables_arr = array_front(&init_ctx.tables);
+ ctx->var_params.providers_arr = array_front(&init_ctx.providers);
+ ctx->var_params.contexts = array_front(&init_ctx.contexts);
+- ctx->var_params.escape_func = init_ctx.escape_func;
+- ctx->var_params.escape_context = init_ctx.escape_context;
++ if (ctx->escape_func != NULL) {
++ /* settings_get_params()'s escape_func overrides all others */
++ ctx->var_params.escape_func = ctx->escape_func;
++ ctx->var_params.escape_context = ctx->escape_context;
++ } else {
++ ctx->var_params.escape_func = init_ctx.escape_func;
++ ctx->var_params.escape_context = init_ctx.escape_context;
++ }
+ ctx->var_params.event = ctx->event;
+ }
+
--- /dev/null
+From cb09189599e28fa8f46790aeb440a35ba8414908 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 23 Feb 2026 19:33:16 +0200
+Subject: [PATCH 07/24] auth: test-auth - Run Lua unit tests even when building
+ Lua as plugin
+
+---
+ src/auth/Makefile.am | 7 +++++--
+ src/auth/test-lua.c | 2 +-
+ src/auth/test-main.c | 2 +-
+ 3 files changed, 7 insertions(+), 4 deletions(-)
+
+--- dovecot-2.4.1+dfsg1.orig/src/auth/Makefile.am
++++ dovecot-2.4.1+dfsg1/src/auth/Makefile.am
+@@ -131,6 +131,8 @@ auth_common_sources = \
+ $(ldap_sources) \
+ $(lua_sources)
+
++test_auth_ldadd_plugins =
++
+ headers = \
+ auth.h \
+ auth-cache.h \
+@@ -185,6 +187,7 @@ libauthdb_lua_la_LDFLAGS = -module -avoi
+ libauthdb_lua_la_LIBADD = $(LIBDOVECOT_LUA)
+ libauthdb_lua_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+ libauthdb_lua_la_SOURCES = $(lua_sources)
++test_auth_ldadd_plugins += libauthdb_lua.la
+ endif
+
+ libauthdb_imap_la_LDFLAGS = -module -avoid-version -shared
+@@ -226,8 +229,8 @@ test_auth_SOURCES = \
+ test-mock.c \
+ test-main.c
+
+-test_auth_LDADD = $(LIBDOVECOT) $(auth_libs) $(AUTH_LIBS) $(LUA_LIBS)
+-test_auth_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(LIBDOVECOT_DEPS)
++test_auth_LDADD = $(test_auth_ldadd_plugins) $(LIBDOVECOT) $(LIBDOVECOT_LUA) $(auth_libs) $(AUTH_LIBS) $(LUA_LIBS)
++test_auth_DEPENDENCIES = $(test_auth_ldadd_plugins) $(pkglibexec_PROGRAMS) $(LIBDOVECOT_DEPS)
+
+ test_mech_SOURCES = \
+ $(auth_common_sources) \
+--- dovecot-2.4.1+dfsg1.orig/src/auth/test-lua.c
++++ dovecot-2.4.1+dfsg1/src/auth/test-lua.c
+@@ -2,7 +2,7 @@
+
+ #include "test-auth.h"
+
+-#ifdef BUILTIN_LUA
++#ifdef HAVE_LUA
+ #include "istream.h"
+ #include "auth-settings.h"
+ #include "auth-request.h"
+--- dovecot-2.4.1+dfsg1.orig/src/auth/test-main.c
++++ dovecot-2.4.1+dfsg1/src/auth/test-main.c
+@@ -22,7 +22,7 @@ int main(int argc, char *argv[])
+ TEST_NAMED(test_auth_request_var_expand)
+ TEST_NAMED(test_auth_request_fields)
+ TEST_NAMED(test_username_filter)
+-#if defined(BUILTIN_LUA)
++#if defined(HAVE_LUA)
+ TEST_NAMED(test_db_lua)
+ #endif
+ { NULL, NULL }
--- /dev/null
+From 46fbe8a5b82d548fcf5cb352bb2510bb7292174a Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 23 Feb 2026 19:54:40 +0200
+Subject: [PATCH 08/24] auth: Rewrite ldap_escape() with a unit test
+
+---
+ src/auth/Makefile.am | 2 +
+ src/auth/db-ldap.c | 89 +++++++++++++++++++++++++++++++++++---------
+ src/auth/db-ldap.h | 1 +
+ src/auth/test-auth.h | 1 +
+ src/auth/test-ldap.c | 39 +++++++++++++++++++
+ src/auth/test-main.c | 3 ++
+ 6 files changed, 117 insertions(+), 18 deletions(-)
+ create mode 100644 src/auth/test-ldap.c
+
+--- dovecot-2.4.1+dfsg1.orig/src/auth/Makefile.am
++++ dovecot-2.4.1+dfsg1/src/auth/Makefile.am
+@@ -178,6 +178,7 @@ libauthdb_ldap_la_LDFLAGS = -module -avo
+ libauthdb_ldap_la_LIBADD = $(LIBDOVECOT_LDAP) $(LDAP_LIBS)
+ libauthdb_ldap_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+ libauthdb_ldap_la_SOURCES = $(ldap_sources)
++test_auth_ldadd_plugins += libauthdb_ldap.la
+ else
+ auth_libs += $(LIBDOVECOT_LDAP)
+ endif
+@@ -225,6 +226,7 @@ test_auth_SOURCES = \
+ test-auth-request-var-expand.c \
+ test-auth-request-fields.c \
+ test-username-filter.c \
++ test-ldap.c \
+ test-lua.c \
+ test-mock.c \
+ test-main.c
+--- dovecot-2.4.1+dfsg1.orig/src/auth/db-ldap.c
++++ dovecot-2.4.1+dfsg1/src/auth/db-ldap.c
+@@ -13,6 +13,7 @@
+ #include "str.h"
+ #include "strescape.h"
+ #include "time-util.h"
++#include "unichar.h"
+ #include "env-util.h"
+ #include "settings.h"
+ #include "ssl-settings.h"
+@@ -54,8 +55,6 @@
+ #define DB_LDAP_REQUEST_MAX_ATTEMPT_COUNT 3
+ #define DB_LDAP_ATTR_DN "~dn"
+
+-static const char *LDAP_ESCAPE_CHARS = "*,\\#+<>;\"()= ";
+-
+ struct db_ldap_result {
+ int refcount;
+ LDAPMessage *msg;
+@@ -1139,26 +1138,80 @@ void db_ldap_get_attribute_names(pool_t
+ *sensitive_r = array_front(&sensitive_attr_names);
+ }
+
+-#define IS_LDAP_ESCAPED_CHAR(c) \
+- ((((unsigned char)(c)) & 0x80) != 0 || strchr(LDAP_ESCAPE_CHARS, (c)) != NULL)
+-
+-const char *ldap_escape(const char *str,
+- void *context ATTR_UNUSED)
++const char *ldap_escape(const char *input, void *context ATTR_UNUSED)
+ {
+- string_t *ret = NULL;
+-
+- for (const char *p = str; *p != '\0'; p++) {
+- if (IS_LDAP_ESCAPED_CHAR(*p)) {
+- if (ret == NULL) {
+- ret = t_str_new((size_t) (p - str) + 64);
+- str_append_data(ret, str, (size_t) (p - str));
+- }
+- str_printfa(ret, "\\%02X", (unsigned char)*p);
+- } else if (ret != NULL)
+- str_append_c(ret, *p);
++ /* This function escapes both LDAP filters and LDAP DNs. This works,
++ because both allow using the method of escaping any characters.
++ However, this isn't really recommended, since apparently some LDAP
++ servers and perhaps other tools don't handle unnecessary escaping
++ correctly.
++
++ Another issue is what to do with invalid UTF-8. LDAP filter RFC
++ suggests just escaping invalid UTF-8. LDAP DN RFC doesn't at least
++ explicitly say that. It instead seems to imply that the string is
++ just expected to be valid UTF-8. Or perhaps to use #hex-string
++ encoding, which isn't compatible with LDAP filters. Just to be safe,
++ we'll replace invalid UTF-8 input with the UTF-8 replacement
++ character.
++
++ So this function isn't perhaps the most standards compliant one, but
++ we don't really care, since this escaping is mainly intended to
++ prevent untrusted username input from breaking out of the filter or
++ DN. Valid usernames are unlikely to require escaping or to be
++ invalid UTF-8. */
++
++ /* Convert invalid UTF-8 characters to replacement characters. */
++ size_t input_len = strlen(input);
++ string_t *str = t_str_new(64);
++ if (!uni_utf8_get_valid_data((const unsigned char *)input,
++ input_len, str)) {
++ /* Input was not valid UTF-8 */
++ input = t_strdup(str_c(str));
++ input_len = str_len(str);
++ str_truncate(str, 0);
++ }
++
++ const char *escape_chars =
++ /* control characters */
++ "\x01\x02\x03\x04\x05\x06\x07\x08"
++ "\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
++ "\x11\x12\x03\x14\x15\x16\x17\x18"
++ "\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++ /* filter + DN */
++ "\\"
++ /* filter only */
++ "*()"
++ /* DN only (not including leading and trailing chars) */
++ ",+<>;\"=";
++ size_t pos;
++
++ /* check the leading character separately (required by DN) */
++ if (input[0] == '#' || input[0] == ' ')
++ pos = 0;
++ else {
++ pos = strcspn(input, escape_chars);
++ if (pos == input_len) {
++ /* the last trailing space is escaped
++ (required by DN) */
++ if (pos > 0 && input[pos - 1] == ' ')
++ pos--;
++ else
++ return input;
++ }
+ }
+
+- return ret == NULL ? str : str_c(ret);
++ do {
++ str_append_data(str, input, pos);
++ str_printfa(str, "\\%02x", (unsigned char)input[pos]);
++ input += pos + 1;
++ input_len -= pos + 1;
++ pos = strcspn(input, escape_chars);
++ /* the last trailing space is escaped (required by DN) */
++ if (pos == input_len && pos > 0 && input[pos - 1] == ' ')
++ pos--;
++ } while (pos < input_len);
++ str_append_data(str, input, pos);
++ return str_c(str);
+ }
+
+ static bool
+--- dovecot-2.4.1+dfsg1.orig/src/auth/db-ldap.h
++++ dovecot-2.4.1+dfsg1/src/auth/db-ldap.h
+@@ -169,6 +169,7 @@ void db_ldap_connect_delayed(struct ldap
+
+ void db_ldap_enable_input(struct ldap_connection *conn, bool enable);
+
++const char *ldap_dn_escape(const char *str, void *context);
+ const char *ldap_escape(const char *str, void *context);
+ const char *ldap_get_error(struct ldap_connection *conn);
+
+--- dovecot-2.4.1+dfsg1.orig/src/auth/test-auth.h
++++ dovecot-2.4.1+dfsg1/src/auth/test-auth.h
+@@ -14,6 +14,7 @@ void test_auth_request_var_expand(void);
+ void test_auth_request_fields(void);
+ void test_db_dict_parse_cache_key(void);
+ void test_username_filter(void);
++void test_db_ldap(void);
+ void test_db_lua(void);
+ struct auth_passdb *passdb_mock(void);
+ void passdb_mock_mod_init(void);
+--- /dev/null
++++ dovecot-2.4.1+dfsg1/src/auth/test-ldap.c
+@@ -0,0 +1,39 @@
++/* Copyright (c) 2026 Dovecot authors, see the included COPYING file */
++
++#include "test-auth.h"
++
++#ifdef HAVE_LDAP
++#include "db-ldap.h"
++
++static void test_ldap_escape(void)
++{
++ struct {
++ const char *input;
++ const char *output;
++ } tests[] = {
++ { "", "" },
++ { " ", "\\20" },
++ { " ", "\\20\\20" },
++ { "foo ", "foo\\20" },
++ { ",", "\\2c" },
++ { "s p a c e", "s p a c e" },
++ { "# start-end#", "\\23 start-end#" },
++ { " start-end2 ", "\\20 start-end2 \\20" },
++ { "middle:,+\"\\<>;=", "middle:\\2c\\2b\\22\\5c\\3c\\3e\\3b\\3d" },
++ { "valid-utf8:\xc3\xb1", "valid-utf8:\xc3\xb1" },
++ { "Bad \xFF Characters", "Bad \xEF\xBF\xBD Characters" },
++ };
++ test_begin("ldap_escape()");
++ for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
++ test_assert_strcmp_idx(ldap_escape(tests[i].input, NULL),
++ tests[i].output, i);
++ }
++ test_end();
++}
++
++void test_db_ldap(void)
++{
++ test_ldap_escape();
++}
++
++#endif
+--- dovecot-2.4.1+dfsg1.orig/src/auth/test-main.c
++++ dovecot-2.4.1+dfsg1/src/auth/test-main.c
+@@ -25,6 +25,9 @@ int main(int argc, char *argv[])
+ #if defined(HAVE_LUA)
+ TEST_NAMED(test_db_lua)
+ #endif
++#if defined(HAVE_LDAP)
++ TEST_NAMED(test_db_ldap)
++#endif
+ { NULL, NULL }
+ };
+
--- /dev/null
+From 568fe0b2f57fd7563d053e5ef3127d3a969b0acc Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 24 Feb 2026 12:24:37 +0200
+Subject: [PATCH 09/24] auth: passdb sql - Fix escaping for set_credentials()
+
+This was only used by OTP SASL mechanism after successful authentication, so
+it practically couldn't be used for SQL injections.
+
+Broken by ef0c63b690e6ef9fbd53cb815dfab50d1667ba3a
+---
+ src/auth/passdb-sql.c | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/src/auth/passdb-sql.c b/src/auth/passdb-sql.c
+index f3682d4926..28291606ea 100644
+--- a/src/auth/passdb-sql.c
++++ b/src/auth/passdb-sql.c
+@@ -258,8 +258,13 @@ static void sql_set_credentials(struct auth_request *request,
+
+ request->mech_password = p_strdup(request->pool, new_credentials);
+
+- if (settings_get(authdb_event(request), &passdb_sql_setting_parser_info, 0,
+- &set, &error) < 0) {
++ const struct settings_get_params params = {
++ .escape_func = passdb_sql_escape,
++ .escape_context = module->db,
++ };
++ if (settings_get_params(authdb_event(request),
++ &passdb_sql_setting_parser_info, ¶ms,
++ &set, &error) < 0) {
+ e_error(authdb_event(request), "%s", error);
+ callback(FALSE, request);
+ return;
+--
+2.39.5
+
--- /dev/null
+From dfce1f1e7214b97ef73fa851655d176455b836fd Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 24 Feb 2026 12:26:46 +0200
+Subject: [PATCH 10/24] auth: userdb sql - Fix escaping for user iteration
+
+This is mostly a non-issue, since userdb iteration doesn't take any
+untrusted input.
+
+Broken by ef0c63b690e6ef9fbd53cb815dfab50d1667ba3a
+---
+ src/auth/userdb-sql.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/src/auth/userdb-sql.c b/src/auth/userdb-sql.c
+index 349f61c49e..09bac48650 100644
+--- a/src/auth/userdb-sql.c
++++ b/src/auth/userdb-sql.c
+@@ -180,9 +180,13 @@ userdb_sql_iterate_init(struct auth_request *auth_request,
+ ctx->ctx.context = context;
+ auth_request_ref(auth_request);
+
+- if (settings_get(authdb_event(auth_request),
+- &userdb_sql_setting_parser_info, 0,
+- &set, &error) < 0) {
++ const struct settings_get_params params = {
++ .escape_func = userdb_sql_escape,
++ .escape_context = module->db,
++ };
++ if (settings_get_params(authdb_event(auth_request),
++ &userdb_sql_setting_parser_info, ¶ms,
++ &set, &error) < 0) {
+ e_error(authdb_event(auth_request), "%s", error);
+ ctx->ctx.failed = TRUE;
+ return &ctx->ctx;
+--
+2.39.5
+
--- /dev/null
+From c6b15902a0e399bd6bf035f7e8869d201e9394d2 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 25 Feb 2026 12:40:22 +0200
+Subject: [PATCH 11/24] lib-var-expand: Add "safe" filter to prevent escaping
+ output
+
+For example ldap_base = %{passdb:next_dn | safe} to avoid escaping the DN.
+---
+ src/lib-var-expand/expansion-filter.c | 9 +++++++++
+ src/lib-var-expand/expansion-program.c | 3 ++-
+ src/lib-var-expand/var-expand-private.h | 1 +
+ 3 files changed, 12 insertions(+), 1 deletion(-)
+
+--- dovecot-2.4.1+dfsg1.orig/src/lib-var-expand/expansion-filter.c
++++ dovecot-2.4.1+dfsg1/src/lib-var-expand/expansion-filter.c
+@@ -1130,6 +1130,14 @@ static int fn_text(const struct var_expa
+ return 0;
+ }
+
++static int fn_safe(const struct var_expand_statement *stmt ATTR_UNUSED,
++ struct var_expand_state *state,
++ const char **error_r ATTR_UNUSED)
++{
++ state->transfer_safe = TRUE;
++ return 0;
++}
++
+ static const struct var_expand_filter var_expand_builtin_filters[] = {
+ { .name = "lookup", .filter = fn_lookup },
+ { .name = "literal", .filter = fn_literal },
+@@ -1167,6 +1175,7 @@ static const struct var_expand_filter va
+ { .name = "text", .filter = fn_text },
+ { .name = "encrypt", .filter = expansion_filter_encrypt },
+ { .name = "decrypt", .filter = expansion_filter_decrypt },
++ { .name = "safe", .filter = fn_safe },
+ { .name = NULL }
+ };
+
+--- dovecot-2.4.1+dfsg1.orig/src/lib-var-expand/expansion-program.c
++++ dovecot-2.4.1+dfsg1/src/lib-var-expand/expansion-program.c
+@@ -140,7 +140,8 @@ int var_expand_program_execute(string_t
+ if (state.transfer_binary)
+ var_expand_state_set_transfer(&state, binary_to_hex(state.transfer->data, state.transfer->used));
+ if (state.transfer_set) {
+- if (!program->only_literal && params->escape_func != NULL) {
++ if (!program->only_literal && !state.transfer_safe &&
++ params->escape_func != NULL) {
+ str_append(state.result,
+ params->escape_func(str_c(state.transfer),
+ params->escape_context));
+--- dovecot-2.4.1+dfsg1.orig/src/lib-var-expand/var-expand-private.h
++++ dovecot-2.4.1+dfsg1/src/lib-var-expand/var-expand-private.h
+@@ -73,6 +73,7 @@ struct var_expand_state {
+ string_t *transfer;
+ bool transfer_set:1;
+ bool transfer_binary:1;
++ bool transfer_safe:1;
+ };
+
+ struct var_expand_statement {
--- /dev/null
+From d75c04e8ccbeb70d66d05938665fe145175ac1b5 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Sun, 29 Mar 2026 19:33:45 +0300
+Subject: [PATCH] lib-var-expand: Reset safe state when transfer is unset
+
+Otherwise unsafe content is treated safe.
+---
+ src/lib-var-expand/test-var-expand.c | 5 ++++-
+ src/lib-var-expand/var-expand.c | 1 +
+ 2 files changed, 5 insertions(+), 1 deletion(-)
+
+Index: dovecot/src/lib-var-expand/test-var-expand.c
+===================================================================
+--- dovecot.orig/src/lib-var-expand/test-var-expand.c
++++ dovecot/src/lib-var-expand/test-var-expand.c
+@@ -611,6 +611,7 @@ static void test_var_expand_escape(void)
+ { .key = "escape", .value = "'hello' \"world\"", },
+ { .key = "first", .value = "bobby" },
+ { .key = "nasty", .value = "\';-- SELECT * FROM bobby.tables" },
++ { .key = "feisty", .value = "' OR '1'='1" },
+ VAR_EXPAND_TABLE_END
+ };
+
+@@ -653,6 +654,10 @@ static void test_var_expand_escape(void)
+ { .in = "%{literal(\"\\\"\\\\hello\\\\world\\\"\")}", .out = "'\"\\hello\\world\"'", .ret = 0 },
+ /* Unsupported escape sequence */
+ { .in = "%{literal('\\z')}", .out = "Invalid character escape", .ret = -1 },
++
++ /* safe filter */
++ { .in = "%{feisty}", "'\\' OR \\'1\\'=\\'1'", .ret = 0 },
++ { .in = "%{clean|safe} and %{feisty}", "hello world and '\\' OR \\'1\\'=\\'1'", .ret = 0 },
+ };
+
+ const struct var_expand_params params = {
+Index: dovecot/src/lib-var-expand/var-expand.c
+===================================================================
+--- dovecot.orig/src/lib-var-expand/var-expand.c
++++ dovecot/src/lib-var-expand/var-expand.c
+@@ -338,6 +338,7 @@ void var_expand_state_set_transfer(struc
+ void var_expand_state_unset_transfer(struct var_expand_state *state)
+ {
+ str_truncate(state->transfer, 0);
++ state->transfer_safe = FALSE;
+ state->transfer_set = FALSE;
+ }
+
--- /dev/null
+From f1bc3ea8ba747fcfe14ab56685d400e4e3cff130 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Mon, 9 Mar 2026 20:04:27 +0200
+Subject: [PATCH 21/24] auth: cache - Use translated username in
+ auth_cache_remove()
+
+---
+ src/auth/auth-cache.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- dovecot-2.4.1+dfsg1.orig/src/auth/auth-cache.c
++++ dovecot-2.4.1+dfsg1/src/auth/auth-cache.c
+@@ -474,7 +474,7 @@ void auth_cache_remove(struct auth_cache
+ {
+ struct auth_cache_node *node;
+
+- key = auth_request_expand_cache_key(request, key, request->fields.user);
++ key = auth_request_expand_cache_key(request, key, request->fields.translated_username);
+ node = hash_table_lookup(cache->hash, key);
+ if (node == NULL)
+ return;
--- /dev/null
+From f2119a25a439f599b0a30fc674300b8f354e5b67 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Wed, 11 Mar 2026 12:30:32 +0200
+Subject: [PATCH 22/24] auth: Move passdb event lifecycle handling to
+ auth_request_passdb_event_(begin|end)
+
+---
+ src/auth/auth-request.c | 39 +++++++++++++++++++++++++++------------
+ src/auth/auth-request.h | 3 +++
+ 2 files changed, 30 insertions(+), 12 deletions(-)
+
+--- dovecot-2.4.1+dfsg1.orig/src/auth/auth-request.c
++++ dovecot-2.4.1+dfsg1/src/auth/auth-request.c
+@@ -663,15 +663,10 @@ auth_request_cache_result_to_str(enum au
+ }
+ }
+
+-void auth_request_passdb_lookup_begin(struct auth_request *request)
++void auth_request_passdb_event_begin(struct auth_request *request)
+ {
+ struct event *event;
+
+- i_assert(request->passdb != NULL);
+- i_assert(!request->userdb_lookup);
+-
+- request->passdb_cache_result = AUTH_REQUEST_CACHE_NONE;
+-
+ /* use passdb-specific settings during the passdb lookup */
+ request->set = request->passdb->auth_set;
+
+@@ -691,11 +686,35 @@ void auth_request_passdb_lookup_begin(st
+ event_set_min_log_level(event, request->passdb->auth_set->verbose ?
+ LOG_TYPE_INFO : LOG_TYPE_WARNING);
+
++ array_push_back(&request->authdb_event, &event);
++}
++
++void auth_request_passdb_event_end(struct auth_request *request)
++{
++ struct event *event = authdb_event(request);
++ event_unref(&event);
++ array_pop_back(&request->authdb_event);
++
++ /* restore protocol-specific settings */
++ request->set = request->protocol_set;
++}
++
++void auth_request_passdb_lookup_begin(struct auth_request *request)
++{
++ struct event *event;
++
++ i_assert(request->passdb != NULL);
++ i_assert(!request->userdb_lookup);
++ auth_request_passdb_event_begin(request);
++
++ event = authdb_event(request);
++
++ request->passdb_cache_result = AUTH_REQUEST_CACHE_NONE;
++
+ e_debug(event_create_passthrough(event)->
+ set_name("auth_passdb_request_started")->
+ event(),
+ "Performing passdb lookup");
+- array_push_back(&request->authdb_event, &event);
+ }
+
+ void auth_request_passdb_lookup_end(struct auth_request *request,
+@@ -713,11 +732,7 @@ void auth_request_passdb_lookup_end(stru
+ request->passdb_cache_result));
+ }
+ e_debug(e->event(), "Finished passdb lookup");
+- event_unref(&event);
+- array_pop_back(&request->authdb_event);
+-
+- /* restore protocol-specific settings */
+- request->set = request->protocol_set;
++ auth_request_passdb_event_end(request);
+ }
+
+ void auth_request_userdb_lookup_begin(struct auth_request *request)
+--- dovecot-2.4.1+dfsg1.orig/src/auth/auth-request.h
++++ dovecot-2.4.1+dfsg1/src/auth/auth-request.h
+@@ -427,6 +427,9 @@ void auth_request_log_finished(struct au
+ void auth_request_master_user_login_finish(struct auth_request *request);
+ void auth_request_fields_init(struct auth_request *request);
+
++void auth_request_passdb_event_begin(struct auth_request *request);
++void auth_request_passdb_event_end(struct auth_request *request);
++
+ void auth_request_passdb_lookup_begin(struct auth_request *request);
+ void auth_request_passdb_lookup_end(struct auth_request *request,
+ enum passdb_result result);
--- /dev/null
+From 8fd279db61be2054c8f3e7275766717318a2df91 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Mon, 9 Mar 2026 21:23:29 +0200
+Subject: [PATCH 23/24] auth: Initialize set_credentials event properly
+
+Fixes update_query
+---
+ src/auth/auth-request.c | 4 ++++
+ src/auth/auth-worker-server.c | 2 ++
+ 2 files changed, 6 insertions(+)
+
+--- dovecot-2.4.1+dfsg1.orig/src/auth/auth-request.c
++++ dovecot-2.4.1+dfsg1/src/auth/auth-request.c
+@@ -1528,6 +1528,8 @@ void auth_request_set_credentials(struct
+ struct auth_passdb *passdb = request->passdb;
+ const char *cache_key, *new_credentials;
+
++ auth_request_passdb_event_begin(request);
++
+ cache_key = passdb_cache == NULL ? NULL : passdb->cache_key;
+ if (cache_key != NULL)
+ auth_cache_remove(passdb_cache, request, cache_key);
+@@ -1544,6 +1546,8 @@ void auth_request_set_credentials(struct
+ /* this passdb doesn't support credentials update */
+ callback(FALSE, request);
+ }
++
++ auth_request_passdb_event_end(request);
+ }
+
+ static void
+--- dovecot-2.4.1+dfsg1.orig/src/auth/auth-worker-server.c
++++ dovecot-2.4.1+dfsg1/src/auth/auth-worker-server.c
+@@ -505,8 +505,10 @@ auth_worker_handle_setcred(struct auth_w
+ }
+ }
+
++ auth_request_passdb_event_begin(auth_request);
+ auth_request->passdb->passdb->iface.
+ set_credentials(auth_request, creds, set_credentials_callback);
++ auth_request_passdb_event_end(auth_request);
+ return TRUE;
+ }
+
--- /dev/null
+From 535a14209810e7b0c8f94479daf580ac4c637904 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Wed, 11 Mar 2026 12:46:53 +0200
+Subject: [PATCH 24/24] auth: passdb-sql - Require update_query to be set when
+ used
+
+---
+ src/auth/passdb-sql.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/src/auth/passdb-sql.c b/src/auth/passdb-sql.c
+index 28291606ea..ec160e3d75 100644
+--- a/src/auth/passdb-sql.c
++++ b/src/auth/passdb-sql.c
+@@ -270,6 +270,12 @@ static void sql_set_credentials(struct auth_request *request,
+ return;
+ }
+
++ if (*set->update_query == '\0') {
++ e_error(authdb_event(request), "passdb_sql_update_query is empty");
++ callback(FALSE, request);
++ return;
++ }
++
+ sql_request = i_new(struct passdb_sql_request, 1);
+ sql_request->auth_request = request;
+ sql_request->callback.set_credentials = callback;
+--
+2.39.5
+
--- /dev/null
+From 6ef282fab8af8faec75b0ff8cb87b11094642ba0 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Wed, 4 Mar 2026 08:05:13 +0200
+Subject: [PATCH 16/24] doveadm: client-connection - Use timing safe credential
+ check
+
+---
+ src/doveadm/client-connection-http.c | 7 +++++--
+ src/doveadm/client-connection-tcp.c | 4 +---
+ 2 files changed, 6 insertions(+), 5 deletions(-)
+
+diff --git a/src/doveadm/client-connection-http.c b/src/doveadm/client-connection-http.c
+index 81ae9f51da..f760548653 100644
+--- a/src/doveadm/client-connection-http.c
++++ b/src/doveadm/client-connection-http.c
+@@ -973,7 +973,9 @@ doveadm_http_server_auth_basic(struct client_request_http *req,
+ value = p_strdup_printf(conn->conn.pool,
+ "doveadm:%s", set->doveadm_password);
+ base64_encode(value, strlen(value), b64_value);
+- if (creds->data != NULL && strcmp(creds->data, str_c(b64_value)) == 0)
++
++ if (creds->data != NULL &&
++ str_equals_timing_almost_safe(value, creds->data))
+ return TRUE;
+
+ e_error(conn->conn.event,
+@@ -1000,7 +1002,8 @@ doveadm_http_server_auth_api_key(struct client_request_http *req,
+ b64_value = str_new(conn->conn.pool, 32);
+ base64_encode(set->doveadm_api_key,
+ strlen(set->doveadm_api_key), b64_value);
+- if (creds->data != NULL && strcmp(creds->data, str_c(b64_value)) == 0)
++ if (creds->data != NULL &&
++ str_equals_timing_almost_safe(creds->data, str_c(b64_value)))
+ return TRUE;
+
+ e_error(conn->conn.event,
+diff --git a/src/doveadm/client-connection-tcp.c b/src/doveadm/client-connection-tcp.c
+index 936bb03f01..cb16f7214b 100644
+--- a/src/doveadm/client-connection-tcp.c
++++ b/src/doveadm/client-connection-tcp.c
+@@ -400,9 +400,7 @@ client_connection_tcp_authenticate(struct client_connection_tcp *conn)
+ return -1;
+ }
+ pass = t_strndup(data + 9, size - 9);
+- if (strlen(pass) != strlen(set->doveadm_password) ||
+- !mem_equals_timing_safe(pass, set->doveadm_password,
+- strlen(pass))) {
++ if (!str_equals_timing_almost_safe(pass, set->doveadm_password)) {
+ e_error(conn->conn.event,
+ "doveadm client authenticated with wrong password");
+ return -1;
+--
+2.39.5
+
--- /dev/null
+From 6d8c50154744284304ceeab69c3951e6d6852007 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Wed, 4 Mar 2026 09:28:18 +0200
+Subject: [PATCH 17/24] doveadm: Use datastack for temporary b64 value
+
+There is no need to allocate it from connection pool.
+---
+ src/doveadm/client-connection-http.c | 14 +++++---------
+ 1 file changed, 5 insertions(+), 9 deletions(-)
+
+diff --git a/src/doveadm/client-connection-http.c b/src/doveadm/client-connection-http.c
+index f760548653..5d927d1979 100644
+--- a/src/doveadm/client-connection-http.c
++++ b/src/doveadm/client-connection-http.c
+@@ -960,7 +960,7 @@ doveadm_http_server_auth_basic(struct client_request_http *req,
+ struct client_connection_http *conn = req->conn;
+ const struct doveadm_settings *set = conn->conn.set;
+ string_t *b64_value;
+- char *value;
++ const char *value;
+
+ if (*set->doveadm_password == '\0') {
+ e_error(conn->conn.event,
+@@ -969,13 +969,11 @@ doveadm_http_server_auth_basic(struct client_request_http *req,
+ return FALSE;
+ }
+
+- b64_value = str_new(conn->conn.pool, 32);
+- value = p_strdup_printf(conn->conn.pool,
+- "doveadm:%s", set->doveadm_password);
+- base64_encode(value, strlen(value), b64_value);
++ value = t_strdup_printf("doveadm:%s", set->doveadm_password);
++ b64_value = t_base64_encode_str(0, UINT_MAX, value);
+
+ if (creds->data != NULL &&
+- str_equals_timing_almost_safe(value, creds->data))
++ str_equals_timing_almost_safe(str_c(b64_value), creds->data))
+ return TRUE;
+
+ e_error(conn->conn.event,
+@@ -999,9 +997,7 @@ doveadm_http_server_auth_api_key(struct client_request_http *req,
+ return FALSE;
+ }
+
+- b64_value = str_new(conn->conn.pool, 32);
+- base64_encode(set->doveadm_api_key,
+- strlen(set->doveadm_api_key), b64_value);
++ b64_value = t_base64_encode_str(0, UINT_MAX, set->doveadm_api_key);
+ if (creds->data != NULL &&
+ str_equals_timing_almost_safe(creds->data, str_c(b64_value)))
+ return TRUE;
+--
+2.39.5
+
--- /dev/null
+From cc628976210de17bd26b4d9761ea879ce78b7980 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Wed, 4 Mar 2026 14:39:43 +0200
+Subject: [PATCH 18/24] doveadm: client-connection - Get API key from
+ per-connection settings
+
+---
+ src/doveadm/client-connection-http.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/doveadm/client-connection-http.c b/src/doveadm/client-connection-http.c
+index 5d927d1979..dfbc3528d7 100644
+--- a/src/doveadm/client-connection-http.c
++++ b/src/doveadm/client-connection-http.c
+@@ -987,7 +987,7 @@ doveadm_http_server_auth_api_key(struct client_request_http *req,
+ const struct http_auth_credentials *creds)
+ {
+ struct client_connection_http *conn = req->conn;
+- const struct doveadm_settings *set = doveadm_settings;
++ const struct doveadm_settings *set = conn->conn.set;
+ string_t *b64_value;
+
+ if (*set->doveadm_api_key == '\0') {
+--
+2.39.5
+
--- /dev/null
+From bc91df5f91e8b83c58f138799d39be687271106d Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 6 Mar 2026 17:06:45 +0200
+Subject: [PATCH 1/2] plugins: imap-filter-sieve: imap-filter-sieve - Adjust to
+ imap_parser_create() API change
+
+---
+ src/plugins/imap-filter-sieve/cmd-filter-sieve.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/pigeonhole/src/plugins/imap-filter-sieve/cmd-filter-sieve.c b/pigeonhole/src/plugins/imap-filter-sieve/cmd-filter-sieve.c
+index 6965b511..33d6804c 100644
+--- a/pigeonhole/src/plugins/imap-filter-sieve/cmd-filter-sieve.c
++++ b/pigeonhole/src/plugins/imap-filter-sieve/cmd-filter-sieve.c
+@@ -379,7 +379,8 @@ bool cmd_filter_sieve(struct client_command_context *cmd)
+ asynchronously the same way as APPEND does. */
+ client->input_lock = cmd;
+ ctx->parser = imap_parser_create(client->input, client->output,
+- client->set->imap_max_line_length);
++ client->set->imap_max_line_length,
++ NULL);
+ if (client->set->imap_literal_minus)
+ imap_parser_enable_literal_minus(ctx->parser);
+ o_stream_unset_flush_callback(client->output);
+--
+2.39.5
+
--- /dev/null
+From ad8e6542518acee4e1a803001b232c03732cdaff Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 6 Mar 2026 15:25:14 +0200
+Subject: [PATCH 12/24] lib-imap, global: Add params parameter to
+ imap_parser_create()
+
+---
+ src/imap-login/imap-login-client.c | 4 ++--
+ src/imap-login/imap-login-cmd-id.c | 4 +++-
+ src/imap/cmd-append.c | 3 ++-
+ src/imap/cmd-setmetadata.c | 3 ++-
+ src/imap/imap-client.c | 3 ++-
+ src/lib-imap-client/imapc-connection.c | 3 ++-
+ src/lib-imap-storage/imap-msgpart.c | 2 +-
+ src/lib-imap/imap-bodystructure.c | 4 ++--
+ src/lib-imap/imap-envelope.c | 2 +-
+ src/lib-imap/imap-parser.c | 3 ++-
+ src/lib-imap/imap-parser.h | 6 +++++-
+ src/lib-imap/test-imap-parser.c | 10 +++++-----
+ src/plugins/virtual/virtual-config.c | 2 +-
+ 13 files changed, 30 insertions(+), 19 deletions(-)
+
+Index: dovecot/src/imap-login/imap-login-client.c
+===================================================================
+--- dovecot.orig/src/imap-login/imap-login-client.c
++++ dovecot/src/imap-login/imap-login-client.c
+@@ -382,7 +382,7 @@ static int imap_client_create(struct cli
+ imap_client->parser =
+ imap_parser_create(imap_client->common.input,
+ imap_client->common.output,
+- IMAP_LOGIN_MAX_LINE_LENGTH);
++ IMAP_LOGIN_MAX_LINE_LENGTH, NULL);
+ struct settings_instance *set_instance = settings_instance_find(client->event);
+ if (set_instance == NULL) {
+ set_instance = settings_instance_new(
+@@ -468,7 +468,7 @@ static void imap_client_starttls(struct
+ imap_client->parser =
+ imap_parser_create(imap_client->common.input,
+ imap_client->common.output,
+- IMAP_LOGIN_MAX_LINE_LENGTH);
++ IMAP_LOGIN_MAX_LINE_LENGTH, NULL);
+
+ /* CRLF is lost from buffer when streams are reopened. */
+ imap_client->skip_line = FALSE;
+Index: dovecot/src/imap-login/imap-login-cmd-id.c
+===================================================================
+--- dovecot.orig/src/imap-login/imap-login-cmd-id.c
++++ dovecot/src/imap-login/imap-login-cmd-id.c
+@@ -346,7 +346,8 @@ int cmd_id(struct imap_client *client)
+ id->params->pool = param_pool;
+ id->parser = imap_parser_create(client->common.input,
+ client->common.output,
+- IMAP_LOGIN_MAX_LINE_LENGTH);
++ IMAP_LOGIN_MAX_LINE_LENGTH,
++ NULL);
+ if (client->set->imap_literal_minus)
+ imap_parser_enable_literal_minus(id->parser);
+ parser_flags = IMAP_PARSE_FLAG_STOP_AT_LIST;
+Index: dovecot/src/imap/cmd-append.c
+===================================================================
+--- dovecot.orig/src/imap/cmd-append.c
++++ dovecot/src/imap/cmd-append.c
+@@ -1063,7 +1063,8 @@ static bool cmd_append_full(struct clien
+ o_stream_unset_flush_callback(client->output);
+
+ ctx->save_parser = imap_parser_create(client->input, client->output,
+- client->set->imap_max_line_length);
++ client->set->imap_max_line_length,
++ NULL);
+ if (client->set->imap_literal_minus)
+ imap_parser_enable_literal_minus(ctx->save_parser);
+
+Index: dovecot/src/imap/cmd-setmetadata.c
+===================================================================
+--- dovecot.orig/src/imap/cmd-setmetadata.c
++++ dovecot/src/imap/cmd-setmetadata.c
+@@ -289,7 +289,8 @@ cmd_setmetadata_start(struct imap_setmet
+ asynchronously the same way as APPEND does. */
+ client->input_lock = cmd;
+ ctx->parser = imap_parser_create(client->input, client->output,
+- client->set->imap_max_line_length);
++ client->set->imap_max_line_length,
++ NULL);
+ if (client->set->imap_literal_minus)
+ imap_parser_enable_literal_minus(ctx->parser);
+ o_stream_unset_flush_callback(client->output);
+Index: dovecot/src/imap/imap-client.c
+===================================================================
+--- dovecot.orig/src/imap/imap-client.c
++++ dovecot/src/imap/imap-client.c
+@@ -975,7 +975,8 @@ client_command_new(struct client *client
+ } else {
+ cmd->parser =
+ imap_parser_create(client->input, client->output,
+- client->set->imap_max_line_length);
++ client->set->imap_max_line_length,
++ NULL);
+ if (client->set->imap_literal_minus)
+ imap_parser_enable_literal_minus(cmd->parser);
+ }
+Index: dovecot/src/lib-imap-client/imapc-connection.c
+===================================================================
+--- dovecot.orig/src/lib-imap-client/imapc-connection.c
++++ dovecot/src/lib-imap-client/imapc-connection.c
+@@ -1876,7 +1876,8 @@ static void imapc_connection_connect_nex
+ o_stream_set_flush_callback(conn->output, imapc_connection_connected,
+ conn);
+ conn->parser = imap_parser_create(conn->input, NULL,
+- conn->client->set->imapc_max_line_length);
++ conn->client->set->imapc_max_line_length,
++ NULL);
+ conn->to = timeout_add(conn->client->set->imapc_connection_timeout_interval_msecs,
+ imapc_connection_timeout, conn);
+ conn->to_output = timeout_add(conn->client->set->imapc_max_idle_time_secs*1000,
+Index: dovecot/src/lib-imap-storage/imap-msgpart.c
+===================================================================
+--- dovecot.orig/src/lib-imap-storage/imap-msgpart.c
++++ dovecot/src/lib-imap-storage/imap-msgpart.c
+@@ -149,7 +149,7 @@ imap_msgpart_get_header_fields(pool_t po
+ int result = 0;
+
+ input = i_stream_create_from_data(header_list, strlen(header_list));
+- parser = imap_parser_create(input, NULL, SIZE_MAX);
++ parser = imap_parser_create(input, NULL, SIZE_MAX, NULL);
+
+ if (imap_parser_finish_line(parser, 0, 0, &args) > 0 &&
+ imap_arg_get_list_full(args, &hdr_list, &list_count) &&
+Index: dovecot/src/lib-imap/imap-bodystructure.c
+===================================================================
+--- dovecot.orig/src/lib-imap/imap-bodystructure.c
++++ dovecot/src/lib-imap/imap-bodystructure.c
+@@ -722,7 +722,7 @@ int imap_bodystructure_parse_full(const
+ input = i_stream_create_from_data(bodystructure, strlen(bodystructure));
+ (void)i_stream_read(input);
+
+- parser = imap_parser_create(input, NULL, SIZE_MAX);
++ parser = imap_parser_create(input, NULL, SIZE_MAX, NULL);
+ ret = imap_parser_finish_line(parser, 0,
+ IMAP_PARSE_FLAG_LITERAL_TYPE, &args);
+ if (ret < 0) {
+@@ -972,7 +972,7 @@ int imap_body_parse_from_bodystructure(c
+ input = i_stream_create_from_data(bodystructure, strlen(bodystructure));
+ (void)i_stream_read(input);
+
+- parser = imap_parser_create(input, NULL, SIZE_MAX);
++ parser = imap_parser_create(input, NULL, SIZE_MAX, NULL);
+ ret = imap_parser_finish_line(parser, 0, IMAP_PARSE_FLAG_NO_UNESCAPE |
+ IMAP_PARSE_FLAG_LITERAL_TYPE, &args);
+ if (ret < 0) {
+Index: dovecot/src/lib-imap/imap-envelope.c
+===================================================================
+--- dovecot.orig/src/lib-imap/imap-envelope.c
++++ dovecot/src/lib-imap/imap-envelope.c
+@@ -222,7 +222,7 @@ bool imap_envelope_parse(const char *env
+ input = i_stream_create_from_data(envelope, strlen(envelope));
+ (void)i_stream_read(input);
+
+- parser = imap_parser_create(input, NULL, SIZE_MAX);
++ parser = imap_parser_create(input, NULL, SIZE_MAX, NULL);
+ ret = imap_parser_finish_line(parser, 0,
+ IMAP_PARSE_FLAG_LITERAL_TYPE, &args);
+ if (ret < 0) {
+Index: dovecot/src/lib-imap/imap-parser.c
+===================================================================
+--- dovecot.orig/src/lib-imap/imap-parser.c
++++ dovecot/src/lib-imap/imap-parser.c
+@@ -69,7 +69,8 @@ struct imap_parser {
+
+ struct imap_parser *
+ imap_parser_create(struct istream *input, struct ostream *output,
+- size_t max_line_size)
++ size_t max_line_size,
++ const struct imap_parser_params *params ATTR_UNUSED)
+ {
+ struct imap_parser *parser;
+
+Index: dovecot/src/lib-imap/imap-parser.h
+===================================================================
+--- dovecot.orig/src/lib-imap/imap-parser.h
++++ dovecot/src/lib-imap/imap-parser.h
+@@ -38,6 +38,9 @@ enum imap_parser_error {
+ IMAP_PARSE_ERROR_LITERAL_TOO_BIG
+ };
+
++struct imap_parser_params {
++};
++
+ struct imap_parser;
+
+ /* Create new IMAP argument parser. output is used for sending command
+@@ -53,7 +56,8 @@ struct imap_parser;
+ 2 * max_line_size. */
+ struct imap_parser *
+ imap_parser_create(struct istream *input, struct ostream *output,
+- size_t max_line_size) ATTR_NULL(2);
++ size_t max_line_size,
++ const struct imap_parser_params *params);
+ void imap_parser_ref(struct imap_parser *parser);
+ void imap_parser_unref(struct imap_parser **parser);
+
+Index: dovecot/src/lib-imap/test-imap-parser.c
+===================================================================
+--- dovecot.orig/src/lib-imap/test-imap-parser.c
++++ dovecot/src/lib-imap/test-imap-parser.c
+@@ -16,7 +16,7 @@ static void test_imap_parser_crlf(void)
+
+ test_begin("imap parser crlf handling");
+ input = test_istream_create(test_input);
+- parser = imap_parser_create(input, NULL, 1024);
++ parser = imap_parser_create(input, NULL, 1024, NULL);
+
+ /* must return -2 until LF is read */
+ for (i = 0; test_input[i] != '\n'; i++) {
+@@ -60,7 +60,7 @@ static void test_imap_parser_partial_lis
+
+ test_begin("imap parser partial list");
+ input = test_istream_create(test_input);
+- parser = imap_parser_create(input, NULL, 1024);
++ parser = imap_parser_create(input, NULL, 1024, NULL);
+
+ (void)i_stream_read(input);
+ test_assert(imap_parser_read_args(parser, 0,
+@@ -128,7 +128,7 @@ static void test_imap_parser_read_tag_cm
+ if (tests[i].type != COMMAND) {
+ input = test_istream_create(tests[i].input);
+ test_assert(i_stream_read(input) > 0);
+- parser = imap_parser_create(input, NULL, 1024);
++ parser = imap_parser_create(input, NULL, 1024, NULL);
+ ret = imap_parser_read_tag(parser, &atom);
+ test_assert_idx(ret == tests[i].ret, i);
+ test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i);
+@@ -139,7 +139,7 @@ static void test_imap_parser_read_tag_cm
+ if (tests[i].type != TAG) {
+ input = test_istream_create(tests[i].input);
+ test_assert(i_stream_read(input) > 0);
+- parser = imap_parser_create(input, NULL, 1024);
++ parser = imap_parser_create(input, NULL, 1024, NULL);
+ ret = imap_parser_read_command_name(parser, &atom);
+ test_assert_idx(ret == tests[i].ret, i);
+ test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i);
+Index: dovecot/src/plugins/virtual/virtual-config.c
+===================================================================
+--- dovecot.orig/src/plugins/virtual/virtual-config.c
++++ dovecot/src/plugins/virtual/virtual-config.c
+@@ -54,7 +54,7 @@ virtual_search_args_parse(const string_t
+ input = i_stream_create_from_data(str_data(rule), str_len(rule));
+ (void)i_stream_read(input);
+
+- imap_parser = imap_parser_create(input, NULL, SIZE_MAX);
++ imap_parser = imap_parser_create(input, NULL, SIZE_MAX, NULL);
+ ret = imap_parser_finish_line(imap_parser, 0, 0, &args);
+ if (ret < 0) {
+ sargs = NULL;
--- /dev/null
+From bb845a61f81be978f641b79058d4bae06f27ee39 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 6 Mar 2026 15:32:29 +0200
+Subject: [PATCH 13/24] lib-imap: Add imap_parser_params.list_count_limit
+
+---
+ src/lib-imap/imap-parser.c | 15 ++++++++++-
+ src/lib-imap/imap-parser.h | 6 +++++
+ src/lib-imap/test-imap-parser.c | 46 +++++++++++++++++++++++++++++++++
+ 3 files changed, 66 insertions(+), 1 deletion(-)
+
+--- dovecot-2.4.1+dfsg1.orig/src/lib-imap/imap-parser.c
++++ dovecot-2.4.1+dfsg1/src/lib-imap/imap-parser.c
+@@ -39,6 +39,7 @@ struct imap_parser {
+ struct istream *input;
+ struct ostream *output;
+ size_t max_line_size;
++ unsigned int list_count_limit;
+ enum imap_parser_flags flags;
+
+ /* reset by imap_parser_reset(): */
+@@ -46,6 +47,7 @@ struct imap_parser {
+ ARRAY_TYPE(imap_arg_list) root_list;
+ ARRAY_TYPE(imap_arg_list) *cur_list;
+ struct imap_arg *list_arg;
++ unsigned int list_count;
+
+ enum arg_parse_type cur_type;
+ size_t cur_pos; /* parser position in input buffer */
+@@ -70,7 +72,7 @@ struct imap_parser {
+ struct imap_parser *
+ imap_parser_create(struct istream *input, struct ostream *output,
+ size_t max_line_size,
+- const struct imap_parser_params *params ATTR_UNUSED)
++ const struct imap_parser_params *params)
+ {
+ struct imap_parser *parser;
+
+@@ -81,6 +83,10 @@ imap_parser_create(struct istream *input
+ parser->input = input;
+ parser->output = output;
+ parser->max_line_size = max_line_size;
++ if (params != NULL && params->list_count_limit > 0)
++ parser->list_count_limit = params->list_count_limit;
++ else
++ parser->list_count_limit = UINT_MAX;
+
+ p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT);
+ parser->cur_list = &parser->root_list;
+@@ -122,6 +128,7 @@ void imap_parser_reset(struct imap_parse
+ p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT);
+ parser->cur_list = &parser->root_list;
+ parser->list_arg = NULL;
++ parser->list_count = 0;
+
+ parser->cur_type = ARG_PARSE_NONE;
+ parser->cur_pos = 0;
+@@ -210,6 +217,12 @@ static bool imap_parser_close_list(struc
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ return FALSE;
+ }
++ if (parser->list_count >= parser->list_count_limit) {
++ parser->error_msg = "Too many '('";
++ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
++ return FALSE;
++ }
++ parser->list_count++;
+
+ arg = imap_arg_create(parser);
+ arg->type = IMAP_ARG_EOL;
+--- dovecot-2.4.1+dfsg1.orig/src/lib-imap/imap-parser.h
++++ dovecot-2.4.1+dfsg1/src/lib-imap/imap-parser.h
+@@ -39,6 +39,12 @@ enum imap_parser_error {
+ };
+
+ struct imap_parser_params {
++ /* How many open lists ('(' chars) to allow before faililng the parsing.
++ 0 means unlimited. This is mainly used to prevent excessive memory
++ usage in imap-login process. In imap process there are many other
++ ways to increase memory usage, so we let the max_line_size be the
++ only limit. */
++ unsigned int list_count_limit;
+ };
+
+ struct imap_parser;
+--- dovecot-2.4.1+dfsg1.orig/src/lib-imap/test-imap-parser.c
++++ dovecot-2.4.1+dfsg1/src/lib-imap/test-imap-parser.c
+@@ -2,6 +2,7 @@
+
+ #include "lib.h"
+ #include "istream.h"
++#include "istream-chain.h"
+ #include "imap-parser.h"
+ #include "test-common.h"
+
+@@ -79,6 +80,50 @@ static void test_imap_parser_partial_lis
+ test_end();
+ }
+
++static void test_imap_parser_list_limit(void)
++{
++ struct {
++ const char *input;
++ int ret;
++ } tests[] = {
++ { "(())\r\n", 1 },
++ { "((()))\r\n", -1 },
++ };
++ struct istream_chain *chain;
++ struct istream *chain_input;
++ struct imap_parser *parser;
++ const struct imap_arg *args;
++
++ test_begin("imap parser list limit");
++ struct imap_parser_params params = {
++ .list_count_limit = 2,
++ };
++
++ for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
++ chain_input = i_stream_create_chain(&chain, SIZE_MAX);
++ parser = imap_parser_create(chain_input, NULL, 1024, ¶ms);
++
++ for (unsigned int j = 0; j < 2; j++) {
++ struct istream *input =
++ test_istream_create(tests[i].input);
++ i_stream_chain_append(chain, input);
++ i_stream_unref(&input);
++
++ (void)i_stream_read(chain_input);
++
++ test_assert_cmp(imap_parser_read_args(parser, 0, 0, &args), ==, tests[i].ret);
++ /* skip over CRLF */
++ i_stream_skip(chain_input, i_stream_get_data_size(chain_input));
++
++ /* make sure parser reset works */
++ imap_parser_reset(parser);
++ }
++ imap_parser_unref(&parser);
++ i_stream_destroy(&chain_input);
++ }
++ test_end();
++}
++
+ static void test_imap_parser_read_tag_cmd(void)
+ {
+ enum read_type {
+@@ -155,6 +200,7 @@ int main(void)
+ static void (*const test_functions[])(void) = {
+ test_imap_parser_crlf,
+ test_imap_parser_partial_list,
++ test_imap_parser_list_limit,
+ test_imap_parser_read_tag_cmd,
+ NULL
+ };
--- /dev/null
+From ab084588f615b7f2bb6e18c8ee9176511b647aca Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Fri, 6 Mar 2026 15:35:12 +0200
+Subject: [PATCH 14/24] imap-login: Limit the number of open IMAP parser lists
+
+This prevents attackers from using a large number of '(' in a command to
+grow memory usage excessively.
+---
+ src/imap-login/imap-login-client.c | 10 ++++++++--
+ src/imap-login/imap-login-client.h | 4 ++++
+ src/imap-login/imap-login-cmd-id.c | 6 +++++-
+ 3 files changed, 17 insertions(+), 3 deletions(-)
+
+Index: dovecot/src/imap-login/imap-login-client.c
+===================================================================
+--- dovecot.orig/src/imap-login/imap-login-client.c
++++ dovecot/src/imap-login/imap-login-client.c
+@@ -379,10 +379,13 @@ static int imap_client_create(struct cli
+ return -1;
+ }
+
++ struct imap_parser_params params = {
++ .list_count_limit = IMAP_LOGIN_LIST_COUNT_LIMIT,
++ };
+ imap_client->parser =
+ imap_parser_create(imap_client->common.input,
+ imap_client->common.output,
+- IMAP_LOGIN_MAX_LINE_LENGTH, NULL);
++ IMAP_LOGIN_MAX_LINE_LENGTH, ¶ms);
+ struct settings_instance *set_instance = settings_instance_find(client->event);
+ if (set_instance == NULL) {
+ set_instance = settings_instance_new(
+@@ -464,11 +467,14 @@ static void imap_client_starttls(struct
+ struct imap_client *imap_client =
+ container_of(client, struct imap_client, common);
+
++ struct imap_parser_params params = {
++ .list_count_limit = IMAP_LOGIN_LIST_COUNT_LIMIT,
++ };
+ imap_parser_unref(&imap_client->parser);
+ imap_client->parser =
+ imap_parser_create(imap_client->common.input,
+ imap_client->common.output,
+- IMAP_LOGIN_MAX_LINE_LENGTH, NULL);
++ IMAP_LOGIN_MAX_LINE_LENGTH, ¶ms);
+
+ /* CRLF is lost from buffer when streams are reopened. */
+ imap_client->skip_line = FALSE;
+Index: dovecot/src/imap-login/imap-login-client.h
+===================================================================
+--- dovecot.orig/src/imap-login/imap-login-client.h
++++ dovecot/src/imap-login/imap-login-client.h
+@@ -11,6 +11,10 @@
+ /* maximum length for IMAP command line. */
+ #define IMAP_LOGIN_MAX_LINE_LENGTH 8192
+
++/* Maximum number of '(' allowed in an IMAP command. Pre-login only uses
++ lists in the ID command. */
++#define IMAP_LOGIN_LIST_COUNT_LIMIT 1
++
+ enum imap_client_id_state {
+ IMAP_CLIENT_ID_STATE_LIST = 0,
+ IMAP_CLIENT_ID_STATE_KEY,
+Index: dovecot/src/imap-login/imap-login-cmd-id.c
+===================================================================
+--- dovecot.orig/src/imap-login/imap-login-cmd-id.c
++++ dovecot/src/imap-login/imap-login-cmd-id.c
+@@ -344,10 +344,14 @@ int cmd_id(struct imap_client *client)
+ client->cmd_id = id = i_new(struct imap_client_cmd_id, 1);
+ id->params = p_new(param_pool, struct imap_id_params, 1);
+ id->params->pool = param_pool;
++
++ struct imap_parser_params params = {
++ .list_count_limit = IMAP_LOGIN_LIST_COUNT_LIMIT,
++ };
+ id->parser = imap_parser_create(client->common.input,
+ client->common.output,
+ IMAP_LOGIN_MAX_LINE_LENGTH,
+- NULL);
++ ¶ms);
+ if (client->set->imap_literal_minus)
+ imap_parser_enable_literal_minus(id->parser);
+ parser_flags = IMAP_PARSE_FLAG_STOP_AT_LIST;
--- /dev/null
+From e0b953af76d754c2984c2867b71fccfef5231bf5 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 2 Mar 2026 13:50:24 +0200
+Subject: [PATCH 15/24] global: Use const for struct imap_parser_params params
+
+---
+ src/imap-login/imap-login-client.c | 4 ++--
+ src/imap-login/imap-login-cmd-id.c | 2 +-
+ src/lib-imap/test-imap-parser.c | 2 +-
+ 3 files changed, 4 insertions(+), 4 deletions(-)
+
+--- dovecot-2.4.1+dfsg1.orig/src/imap-login/imap-login-client.c
++++ dovecot-2.4.1+dfsg1/src/imap-login/imap-login-client.c
+@@ -379,7 +379,7 @@ static int imap_client_create(struct cli
+ return -1;
+ }
+
+- struct imap_parser_params params = {
++ const struct imap_parser_params params = {
+ .list_count_limit = IMAP_LOGIN_LIST_COUNT_LIMIT,
+ };
+ imap_client->parser =
+@@ -467,7 +467,7 @@ static void imap_client_starttls(struct
+ struct imap_client *imap_client =
+ container_of(client, struct imap_client, common);
+
+- struct imap_parser_params params = {
++ const struct imap_parser_params params = {
+ .list_count_limit = IMAP_LOGIN_LIST_COUNT_LIMIT,
+ };
+ imap_parser_unref(&imap_client->parser);
+--- dovecot-2.4.1+dfsg1.orig/src/imap-login/imap-login-cmd-id.c
++++ dovecot-2.4.1+dfsg1/src/imap-login/imap-login-cmd-id.c
+@@ -345,7 +345,7 @@ int cmd_id(struct imap_client *client)
+ id->params = p_new(param_pool, struct imap_id_params, 1);
+ id->params->pool = param_pool;
+
+- struct imap_parser_params params = {
++ const struct imap_parser_params params = {
+ .list_count_limit = IMAP_LOGIN_LIST_COUNT_LIMIT,
+ };
+ id->parser = imap_parser_create(client->common.input,
+--- dovecot-2.4.1+dfsg1.orig/src/lib-imap/test-imap-parser.c
++++ dovecot-2.4.1+dfsg1/src/lib-imap/test-imap-parser.c
+@@ -95,7 +95,7 @@ static void test_imap_parser_list_limit(
+ const struct imap_arg *args;
+
+ test_begin("imap parser list limit");
+- struct imap_parser_params params = {
++ const struct imap_parser_params params = {
+ .list_count_limit = 2,
+ };
+
--- /dev/null
+From fcb339d39bfb19abf1bb88711112f49c1c589b1a Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 2 Mar 2026 14:40:57 +0200
+Subject: [PATCH 2/2] managesieve-login: Verify AUTHENTICATE initial response
+ size isn't too large
+
+This prevents DoSing the managesieve-login by sending an excessively large
+initial response size, which causes a huge memory allocation.
+---
+ src/managesieve-login/client-authenticate.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/pigeonhole/src/managesieve-login/client-authenticate.c b/pigeonhole/src/managesieve-login/client-authenticate.c
+index 822dae6a..5aeccd35 100644
+--- a/pigeonhole/src/managesieve-login/client-authenticate.c
++++ b/pigeonhole/src/managesieve-login/client-authenticate.c
+@@ -196,6 +196,11 @@ managesieve_client_auth_read_response(struct managesieve_client *msieve_client,
+ if (i_stream_get_size(msieve_client->auth_response_input,
+ FALSE, &resp_size) <= 0)
+ resp_size = 0;
++ else if (resp_size > LOGIN_MAX_AUTH_BUF_SIZE) {
++ client_destroy(client,
++ "Authentication response too large");
++ return -1;
++ }
+
+ if (client->auth_response == NULL) {
+ client->auth_response =
+--
+2.39.5
+
--- /dev/null
+From 4041a0cb7bbaa6faf38670ab92afc1039dd0d0d5 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 24 Feb 2026 13:11:14 +0200
+Subject: [PATCH 03/24] lib-mail: Limit the number of RFC2231 parameters that
+ can be parsed
+
+This avoids excessive CPU usage especially in result_append().
+---
+ src/lib-mail/rfc2231-parser.c | 4 +++-
+ src/lib-mail/rfc822-parser.h | 5 +++++
+ src/lib-mail/test-rfc2231-parser.c | 30 ++++++++++++++++++++++++++++++
+ 3 files changed, 38 insertions(+), 1 deletion(-)
+
+--- dovecot-2.4.1+dfsg1.orig/src/lib-mail/rfc2231-parser.c
++++ dovecot-2.4.1+dfsg1/src/lib-mail/rfc2231-parser.c
+@@ -203,7 +203,7 @@ int rfc2231_parse(struct rfc822_parser_c
+ struct rfc2231_parameter rfc2231_param;
+ const char *key, *p, *p2;
+ string_t *str;
+- unsigned int i, j, count, next, next_idx;
++ unsigned int i, j, count, next, next_idx, params_count = 0;
+ bool ok, broken = FALSE;
+ const char *prev_replacement_str;
+ int ret;
+@@ -221,6 +221,8 @@ int rfc2231_parse(struct rfc822_parser_c
+ t_array_init(&rfc2231_params_arr, 8);
+ str = t_str_new(64);
+ while ((ret = rfc822_parse_content_param(ctx, &key, str)) != 0) {
++ if (++params_count > RFC2231_MAX_PARAMS)
++ break;
+ if (ret < 0) {
+ /* try to continue anyway.. */
+ broken = TRUE;
+--- dovecot-2.4.1+dfsg1.orig/src/lib-mail/rfc822-parser.h
++++ dovecot-2.4.1+dfsg1/src/lib-mail/rfc822-parser.h
+@@ -3,6 +3,11 @@
+
+ #include "unichar.h"
+
++/* Maximum number of parameters to parse. After this the rest of the parameters
++ are skipped. This is to avoid excessive CPU usage that can be caused by
++ merging of these parameters. */
++#define RFC2231_MAX_PARAMS 128
++
+ /* This can be used as a common NUL replacement character */
+ #define RFC822_NUL_REPLACEMENT_STR UNICODE_REPLACEMENT_CHAR_UTF8
+
+--- dovecot-2.4.1+dfsg1.orig/src/lib-mail/test-rfc2231-parser.c
++++ dovecot-2.4.1+dfsg1/src/lib-mail/test-rfc2231-parser.c
+@@ -1,6 +1,7 @@
+ /* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+ #include "lib.h"
++#include "str.h"
+ #include "rfc822-parser.h"
+ #include "rfc2231-parser.h"
+ #include "test-common.h"
+@@ -249,6 +250,34 @@ static void test_rfc2231_parser_multi_en
+ run_test("rfc2231 parser invalid encoding", input, sizeof(input)-1, output);
+ }
+
++static void test_rfc2231_parser_limits(void)
++{
++ string_t *input = t_str_new(1024);
++
++ test_begin("rfc2231 parser limits");
++ str_append(input, "; ");
++ for (unsigned int i = 0; i < 1100; i++)
++ str_printfa(input, "a%u=b%u; ", i, i);
++ struct rfc822_parser_context parser;
++ const char *const *result;
++ rfc822_parser_init(&parser, str_data(input), str_len(input), NULL);
++ test_assert(rfc2231_parse(&parser, &result) == 0);
++
++ unsigned int count = str_array_length(result);
++ test_assert(count == RFC2231_MAX_PARAMS * 2);
++ for (unsigned int i = 0; i < count; i += 2) {
++ str_truncate(input, 0);
++ str_printfa(input, "a%u", i / 2);
++ test_assert_strcmp_idx(result[i], str_c(input), i);
++
++ str_truncate(input, 0);
++ str_printfa(input, "b%u", i / 2);
++ test_assert_strcmp_idx(result[i + 1], str_c(input), i);
++ }
++ rfc822_parser_deinit(&parser);
++ test_end();
++}
++
+ int main(void)
+ {
+ static void (*const test_functions[])(void) = {
+@@ -264,6 +293,7 @@ int main(void)
+ test_rfc2231_parser_encodings,
+ test_rfc2231_parser_utf8_encoding,
+ test_rfc2231_parser_multi_encodings,
++ test_rfc2231_parser_limits,
+ NULL
+ };
+ return test_run(test_functions);
--- /dev/null
+From c1c53885bda550632b944dd305013cd010e0e058 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Wed, 8 Apr 2026 11:33:11 +0300
+Subject: [PATCH] login-common: Only accept base64 in sasl
+
+---
+ src/login-common/client-common-auth.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+Index: dovecot/src/login-common/client-common-auth.c
+===================================================================
+--- dovecot.orig/src/login-common/client-common-auth.c
++++ dovecot/src/login-common/client-common-auth.c
+@@ -3,6 +3,7 @@
+ #include "hostpid.h"
+ #include "login-common.h"
+ #include "array.h"
++#include "base64.h"
+ #include "iostream.h"
+ #include "istream.h"
+ #include "ostream.h"
+@@ -865,6 +866,14 @@ void client_auth_respond(struct client *
+ return;
+ }
+
++ /* Only accept base64 */
++ for (size_t i = 0; response[i] != '\0'; i++) {
++ if (!base64_is_valid_char(response[i]) && response[i] != '=') {
++ client_auth_fail(client, "Invalid base64 in response");
++ return;
++ }
++ }
++
+ client->auth_client_continue_pending = FALSE;
+ client_set_auth_waiting(client);
+ sasl_server_auth_continue(client, response);
--- /dev/null
+From 5b0ed9d1034c023d3daf218b6b8656f0cdd383dc Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Sun, 19 Apr 2026 23:10:29 +0000
+Subject: [PATCH] lib-sieve: Enforce CPU time limit within :contains and
+ :matches matcher loops
+
+The naive O(N*M) substring search in mcht-contains.c and the naive find loop
+in mcht-matches.c can run for hours on a large value (e.g. a message body),
+completely bypassing sieve_max_cpu_time because that limit was only checked
+between bytecode operations.
+
+Expose the active CPU limit via sieve_runtime_cpu_limit_exceeded() and poll
+it every 4096 inner iterations. When the limit is hit the match returns
+SIEVE_EXEC_RESOURCE_LIMIT, matching the existing behavior at the bytecode
+boundary. This is a minimal safety net ahead of switching the matchers to
+algorithms that do not require it.
+---
+ src/lib-sieve/mcht-contains.c | 21 ++++++++++++
+ src/lib-sieve/mcht-matches.c | 57 +++++++++++++++++++++++++++----
+ src/lib-sieve/sieve-interpreter.c | 19 +++++++++++
+ src/lib-sieve/sieve-interpreter.h | 12 +++++++
+ 4 files changed, 103 insertions(+), 6 deletions(-)
+
+Index: dovecot/pigeonhole/src/lib-sieve/mcht-contains.c
+===================================================================
+--- dovecot.orig/pigeonhole/src/lib-sieve/mcht-contains.c
++++ dovecot/pigeonhole/src/lib-sieve/mcht-contains.c
+@@ -8,6 +8,7 @@
+
+ #include "sieve-match-types.h"
+ #include "sieve-comparators.h"
++#include "sieve-interpreter.h"
+ #include "sieve-match.h"
+
+ #include <string.h>
+@@ -38,7 +39,14 @@ const struct sieve_match_type_def contai
+
+ /* FIXME: Naive substring match implementation. Should switch to more
+ * efficient algorithm if large values need to be searched (e.g. message body).
++ *
++ * The inner loop polls the interpreter CPU time limit periodically so that a
++ * single O(N*M) match on a large value cannot run for many times the
++ * configured sieve_max_cpu_time (which is otherwise only checked between
++ * bytecode operations).
+ */
++#define SIEVE_CONTAINS_CPU_CHECK_INTERVAL 4096
++
+ static int mcht_contains_match_key
+ (struct sieve_match_context *mctx, const char *val, size_t val_size,
+ const char *key, size_t key_size)
+@@ -48,6 +56,7 @@ static int mcht_contains_match_key
+ const char *kend = (const char *) key + key_size;
+ const char *vp = val;
+ const char *kp = key;
++ unsigned int counter = 0;
+
+ if ( val_size == 0 )
+ return ( key_size == 0 ? 1 : 0 );
+@@ -58,6 +67,18 @@ static int mcht_contains_match_key
+ while ( (vp < vend) && (kp < kend) ) {
+ if ( !cmp->def->char_match(cmp, &vp, vend, &kp, kend) )
+ vp++;
++
++ if ( ++counter >= SIEVE_CONTAINS_CPU_CHECK_INTERVAL ) {
++ counter = 0;
++ if ( sieve_runtime_cpu_limit_exceeded(mctx->runenv) ) {
++ sieve_runtime_error(
++ mctx->runenv, NULL,
++ "execution exceeded CPU time limit");
++ mctx->exec_status =
++ SIEVE_EXEC_RESOURCE_LIMIT;
++ return -1;
++ }
++ }
+ }
+
+ return ( kp == kend ? 1 : 0 );
+Index: dovecot/pigeonhole/src/lib-sieve/mcht-matches.c
+===================================================================
+--- dovecot.orig/pigeonhole/src/lib-sieve/mcht-matches.c
++++ dovecot/pigeonhole/src/lib-sieve/mcht-matches.c
+@@ -9,6 +9,7 @@
+
+ #include "sieve-match-types.h"
+ #include "sieve-comparators.h"
++#include "sieve-interpreter.h"
+ #include "sieve-match.h"
+
+ #include <string.h>
+@@ -46,16 +47,38 @@ const struct sieve_match_type_def matche
+ #endif
+
+ /* FIXME: Naive implementation, substitute this with dovecot src/lib/str-find.c
++ *
++ * The inner loop polls the interpreter CPU time limit periodically so that a
++ * single O(N*M) search on a large value cannot run for many times the
++ * configured sieve_max_cpu_time. Returns 1 on match, 0 on exhaustion, or -1
++ * when the CPU time limit was exceeded (mctx->exec_status is set).
+ */
+-static inline bool _string_find(const struct sieve_comparator *cmp,
+- const char **valp, const char *vend, const char **keyp, const char *kend)
++#define SIEVE_MATCHES_CPU_CHECK_INTERVAL 4096
++
++static int
++_string_find(struct sieve_match_context *mctx,
++ const struct sieve_comparator *cmp,
++ const char **valp, const char *vend,
++ const char **keyp, const char *kend,
++ unsigned int *counter)
+ {
+ while ( (*valp < vend) && (*keyp < kend) ) {
+ if ( !cmp->def->char_match(cmp, valp, vend, keyp, kend) )
+ (*valp)++;
++ if (++(*counter) >= SIEVE_MATCHES_CPU_CHECK_INTERVAL) {
++ *counter = 0;
++ if (sieve_runtime_cpu_limit_exceeded(mctx->runenv)) {
++ sieve_runtime_error(
++ mctx->runenv, NULL,
++ "execution exceeded CPU time limit");
++ mctx->exec_status =
++ SIEVE_EXEC_RESOURCE_LIMIT;
++ return -1;
++ }
++ }
+ }
+
+- return (*keyp == kend);
++ return (*keyp == kend ? 1 : 0);
+ }
+
+ static char _scan_key_section
+@@ -93,6 +116,7 @@ static int mcht_matches_match_key
+ char wcard = '\0'; /* Current wildcard */
+ char next_wcard = '\0'; /* Next widlcard */
+ unsigned int key_offset = 0;
++ unsigned int counter = 0;
+
+ if ( cmp->def == NULL || cmp->def->char_match == NULL )
+ return 0;
+@@ -134,6 +158,19 @@ static int mcht_matches_match_key
+ while (kp < kend && vp < vend ) {
+ const char *needle, *nend;
+
++ if (++counter >= SIEVE_MATCHES_CPU_CHECK_INTERVAL) {
++ counter = 0;
++ if (sieve_runtime_cpu_limit_exceeded(mctx->runenv)) {
++ sieve_runtime_error(
++ mctx->runenv, NULL,
++ "execution exceeded CPU time limit");
++ mctx->exec_status =
++ SIEVE_EXEC_RESOURCE_LIMIT;
++ sieve_match_values_abort(&mvalues);
++ return -1;
++ }
++ }
++
+ if ( !backtrack ) {
+ /* Search the next '*' wildcard in the key string */
+
+@@ -268,10 +305,20 @@ static int mcht_matches_match_key
+
+ /* Match may happen at any offset (>= key offset): find substring */
+ vp += key_offset;
+- if ( (vp >= vend) || !_string_find(cmp, &vp, vend, &needle, nend) ) {
++ if (vp >= vend) {
+ debug_printf(" failed to find needle at an offset\n");
+ break;
+ }
++ int fres = _string_find(mctx, cmp, &vp, vend,
++ &needle, nend, &counter);
++ if (fres < 0) {
++ sieve_match_values_abort(&mvalues);
++ return -1;
++ }
++ if (fres == 0) {
++ debug_printf(" failed to find needle at an offset\n");
++ break;
++ }
+
+ prv = vp - str_len(section);
+ prk = kp;
+Index: dovecot/pigeonhole/src/lib-sieve/sieve-interpreter.c
+===================================================================
+--- dovecot.orig/pigeonhole/src/lib-sieve/sieve-interpreter.c
++++ dovecot/pigeonhole/src/lib-sieve/sieve-interpreter.c
+@@ -85,6 +85,13 @@ struct sieve_interpreter {
+ struct sieve_runtime_trace trace;
+ struct sieve_resource_usage rusage;
+
++ /* CPU time limit for the current sieve_interpreter_continue() call;
++ NULL when no limit is configured or not currently executing. Exposed
++ via sieve_runtime_cpu_limit_exceeded() so long-running runtime code
++ can enforce the limit without waiting for the next bytecode
++ boundary. */
++ struct cpu_limit *climit;
++
+ /* Current operation */
+ struct sieve_operation oprtn;
+
+@@ -362,6 +369,15 @@ sieve_interpreter_svinst(struct sieve_in
+ return interp->runenv.exec_env->svinst;
+ }
+
++bool sieve_runtime_cpu_limit_exceeded(const struct sieve_runtime_env *renv)
++{
++ struct sieve_interpreter *interp = renv->interp;
++
++ if (interp->climit == NULL)
++ return FALSE;
++ return cpu_limit_exceeded(interp->climit);
++}
++
+ /* Do not use this function for normal sieve extensions. This is intended for
+ * the testsuite only.
+ */
+@@ -939,6 +955,7 @@ int sieve_interpreter_continue(struct si
+ climit = cpu_limit_init(svinst->set->max_cpu_time,
+ CPU_LIMIT_TYPE_USER);
+ }
++ interp->climit = climit;
+
+ while (ret == SIEVE_EXEC_OK && !interp->interrupted &&
+ *address < sieve_binary_block_get_size(renv->sblock)) {
+@@ -959,6 +976,8 @@ int sieve_interpreter_continue(struct si
+ ret = sieve_interpreter_operation_execute(interp);
+ }
+
++ interp->climit = NULL;
++
+ if (climit != NULL) {
+ sieve_resource_usage_init(&rusage);
+ rusage.cpu_time_msecs =
+Index: dovecot/pigeonhole/src/lib-sieve/sieve-interpreter.h
+===================================================================
+--- dovecot.orig/pigeonhole/src/lib-sieve/sieve-interpreter.h
++++ dovecot/pigeonhole/src/lib-sieve/sieve-interpreter.h
+@@ -165,6 +165,18 @@ int sieve_interpreter_run(struct sieve_i
+ struct sieve_result *result);
+
+ /*
++ * CPU limit
++ */
++
++/* Returns TRUE if the current interpreter execution has exceeded its CPU
++ time limit (sieve_max_cpu_time). Callable from within long-running runtime
++ code (e.g. matcher inner loops) so that limit enforcement is not deferred
++ until the next bytecode boundary. Returns FALSE if no limit is active or
++ no execution is currently in progress. Cheap: does not call getrusage()
++ on each invocation. */
++bool sieve_runtime_cpu_limit_exceeded(const struct sieve_runtime_env *renv);
++
++/*
+ * Error handling
+ */
+
--- /dev/null
+From b7daa4104ff064c1fb549540cc9d96c2d9e2509c Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 22 Apr 2026 15:43:58 +0300
+Subject: [PATCH 1/3] acl: Add acl_id_is_valid()
+
+Returns TRUE if the ACL identifier string is at most ACL_ID_MAX_LEN
+(1024) bytes long, contains no control characters and is valid UTF-8.
+---
+ src/plugins/acl/acl-rights.c | 16 ++++++++++++++++
+ src/plugins/acl/acl-rights.h | 6 ++++++
+ 2 files changed, 22 insertions(+)
+
+diff --git a/src/plugins/acl/acl-rights.c b/src/plugins/acl/acl-rights.c
+index 654ee00aef..5729f8b52a 100644
+--- a/src/plugins/acl/acl-rights.c
++++ b/src/plugins/acl/acl-rights.c
+@@ -3,11 +3,14 @@
+ #include "lib.h"
+ #include "array.h"
+ #include "str.h"
++#include "unichar.h"
+ /* <settings checks> */
+ #include "strescape.h"
+ /* </settings checks> */
+ #include "acl-api-private.h"
+
++#include <ctype.h>
++
+ /* <settings checks> */
+ const struct acl_letter_map acl_letter_map[] = {
+ { 'l', MAIL_ACL_LOOKUP },
+@@ -44,6 +47,19 @@ static_assert(N_ELEMENTS(acl_letter_map) == N_ELEMENTS(all_mailbox_rights),
+
+ /* </settings checks> */
+
++bool acl_id_is_valid(const char *id)
++{
++ size_t len = strlen(id);
++
++ if (len > ACL_ID_MAX_LEN)
++ return FALSE;
++ for (size_t i = 0; i < len; i++) {
++ if (i_iscntrl(id[i]))
++ return FALSE;
++ }
++ return uni_utf8_data_is_valid((const unsigned char *)id, len);
++}
++
+ void acl_rights_write_id(string_t *dest, const struct acl_rights *right)
+ {
+ switch (right->id_type) {
+diff --git a/src/plugins/acl/acl-rights.h b/src/plugins/acl/acl-rights.h
+index 88ef73e3b4..32163e7c3d 100644
+--- a/src/plugins/acl/acl-rights.h
++++ b/src/plugins/acl/acl-rights.h
+@@ -34,6 +34,8 @@
+ #define ACL_ID_NAME_GROUP_PREFIX "group="
+ #define ACL_ID_NAME_GROUP_OVERRIDE_PREFIX "group-override="
+
++#define ACL_ID_MAX_LEN 1024
++
+ struct acl_letter_map {
+ const char letter;
+ const char *name;
+@@ -104,6 +106,10 @@ struct acl_rights_update {
+ time_t last_change;
+ };
+
++/* Returns TRUE if the ACL identifier string is valid: no longer than
++ ACL_ID_MAX_LEN bytes, no control characters and valid UTF-8. */
++bool acl_id_is_valid(const char *id);
++
+ /* Returns the canonical ID for the right. */
+ const char *acl_rights_get_id(const struct acl_rights *right);
+
+--
+2.47.3
+
--- /dev/null
+From 20b48c3db5fed7ccaa8e0a4c10ca54f6dc36a63d Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 22 Apr 2026 15:44:24 +0300
+Subject: [PATCH 2/3] imap-acl: Fail if ACL identifier is invalid
+
+Reject invalid identifiers early in imap_acl_identifier_parse() using
+acl_id_is_valid(). This prevents CR/LF injection and rejects identifiers
+that are too long, contain control characters or are not valid UTF-8.
+---
+ src/plugins/imap-acl/imap-acl-plugin.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+Index: dovecot/src/plugins/imap-acl/imap-acl-plugin.c
+===================================================================
+--- dovecot.orig/src/plugins/imap-acl/imap-acl-plugin.c
++++ dovecot/src/plugins/imap-acl/imap-acl-plugin.c
+@@ -872,6 +872,11 @@ imap_acl_identifier_parse(struct client_
+ allow_anyone = set->allow_anyone;
+ settings_free(set);
+
++ if (!acl_id_is_valid(id)) {
++ *client_error_r = "Invalid identifier";
++ return -1;
++ }
++
+ if (str_begins_with(id, IMAP_ACL_GLOBAL_PREFIX)) {
+ *client_error_r = t_strdup_printf(
+ "Global ACLs can't be modified: %s", id);
--- /dev/null
+From 1cf6ad1a119e5dace816e401e73ba6cc11d1472e Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Wed, 22 Apr 2026 15:45:00 +0300
+Subject: [PATCH 3/3] acl: Assert-crash if ACL identifier is invalid before
+ writing it
+
+It should have been checked earlier already.
+---
+ src/plugins/acl/acl-backend-vfile-update.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+Index: dovecot/src/plugins/acl/acl-backend-vfile-update.c
+===================================================================
+--- dovecot.orig/src/plugins/acl/acl-backend-vfile-update.c
++++ dovecot/src/plugins/acl/acl-backend-vfile-update.c
+@@ -119,6 +119,7 @@ vfile_write_right(string_t *dest, const
+ if (neg) str_append_c(dest,'-');
+ acl_rights_write_id(dest, right);
+
++ i_assert(acl_id_is_valid(str_c(dest)));
+ if (strchr(str_c(dest), ' ') != NULL) T_BEGIN {
+ /* need to escape it */
+ const char *escaped = t_strdup(str_escape(str_c(dest)));
--- /dev/null
+From da1438c76b797f055d4ad7f0eaa17e5e29ca31ee Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 27 Apr 2026 17:40:46 +0300
+Subject: [PATCH] lib-imap: Fix imap_parser_params.list_count_limit to actually
+ work
+
+The previous fix in d0f67b52914565a35f3817335ab9633cb291513c was
+accidentally limiting the number of ')', not the number of '('.
+---
+ src/lib-imap/imap-parser.c | 19 +++++++++++--------
+ src/lib-imap/test-imap-parser.c | 14 ++++++++++++--
+ 2 files changed, 23 insertions(+), 10 deletions(-)
+
+diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c
+index 6212aed33d..b1df3d7b78 100644
+--- a/src/lib-imap/imap-parser.c
++++ b/src/lib-imap/imap-parser.c
+@@ -191,8 +191,15 @@ static struct imap_arg *imap_arg_create(struct imap_parser *parser)
+ return arg;
+ }
+
+-static void imap_parser_open_list(struct imap_parser *parser)
++static bool imap_parser_open_list(struct imap_parser *parser)
+ {
++ if (parser->list_count >= parser->list_count_limit) {
++ parser->error_msg = "Too many '('";
++ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
++ return FALSE;
++ }
++ parser->list_count++;
++
+ parser->list_arg = imap_arg_create(parser);
+ parser->list_arg->type = IMAP_ARG_LIST;
+ p_array_init(&parser->list_arg->_data.list, parser->pool,
+@@ -200,6 +207,7 @@ static void imap_parser_open_list(struct imap_parser *parser)
+ parser->cur_list = &parser->list_arg->_data.list;
+
+ parser->cur_type = ARG_PARSE_NONE;
++ return TRUE;
+ }
+
+ static bool imap_parser_close_list(struct imap_parser *parser)
+@@ -217,12 +225,6 @@ static bool imap_parser_close_list(struct imap_parser *parser)
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ return FALSE;
+ }
+- if (parser->list_count >= parser->list_count_limit) {
+- parser->error_msg = "Too many '('";
+- parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+- return FALSE;
+- }
+- parser->list_count++;
+
+ arg = imap_arg_create(parser);
+ arg->type = IMAP_ARG_EOL;
+@@ -673,7 +675,8 @@ static bool imap_parser_read_arg(struct imap_parser *parser)
+ parser->literal8 = FALSE;
+ break;
+ case '(':
+- imap_parser_open_list(parser);
++ if (!imap_parser_open_list(parser))
++ return FALSE;
+ if ((parser->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) != 0) {
+ i_stream_skip(parser->input, 1);
+ return FALSE;
+diff --git a/src/lib-imap/test-imap-parser.c b/src/lib-imap/test-imap-parser.c
+index fb7c308a23..d509c8176d 100644
+--- a/src/lib-imap/test-imap-parser.c
++++ b/src/lib-imap/test-imap-parser.c
+@@ -85,9 +85,15 @@ static void test_imap_parser_list_limit(void)
+ struct {
+ const char *input;
+ int ret;
++ const char *error;
+ } tests[] = {
+- { "(())\r\n", 1 },
+- { "((()))\r\n", -1 },
++ { "(())\r\n", 1, NULL },
++ { "((\r\n", -1, "Missing ')'" },
++ { "(()\r\n", -1, "Missing ')'" },
++ { "(()))\r\n", -1, "Unexpected ')'" },
++ { "((()))\r\n", -1, "Too many '('" },
++ { "(({10}\r\n", -2, NULL },
++ { "((({10}\r\n", -1, "Too many '('" },
+ };
+ struct istream_chain *chain;
+ struct istream *chain_input;
+@@ -112,6 +118,10 @@ static void test_imap_parser_list_limit(void)
+ (void)i_stream_read(chain_input);
+
+ test_assert_cmp(imap_parser_read_args(parser, 0, 0, &args), ==, tests[i].ret);
++ if (tests[i].ret == -1) {
++ enum imap_parser_error err;
++ test_assert_strcmp_idx(imap_parser_get_error(parser, &err), tests[i].error, i);
++ }
+ /* skip over CRLF */
+ i_stream_skip(chain_input, i_stream_get_data_size(chain_input));
+
+--
+2.47.3
+
--- /dev/null
+From: =?utf-8?q?Christian_G=C3=B6ttsche?= <cgzones@googlemail.com>
+Date: Thu, 22 Dec 2022 17:00:53 +0100
+Subject: Use _FORTIFY_SOURCE level 3
+
+Forwarded: not-needed
+---
+ m4/dovecot.m4 | 6 +++---
+ pigeonhole/m4/dovecot.m4 | 6 +++---
+ 2 files changed, 6 insertions(+), 6 deletions(-)
+
+Index: dovecot/m4/dovecot.m4
+===================================================================
+--- dovecot.orig/m4/dovecot.m4
++++ dovecot/m4/dovecot.m4
+@@ -9,7 +9,7 @@ dnl modifications, as long as this notic
+ # serial 42
+
+ dnl
+-dnl Check for support for D_FORTIFY_SOURCE=2
++dnl Check for support for D_FORTIFY_SOURCE=3
+ dnl
+
+ AC_DEFUN([AC_CC_D_FORTIFY_SOURCE],[
+@@ -17,8 +17,8 @@ AC_DEFUN([AC_CC_D_FORTIFY_SOURCE],[
+ AS_IF([test "$enable_hardening" = yes], [
+ case "$host" in
+ *)
+- gl_COMPILER_OPTION_IF([-O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2], [
+- CFLAGS="$CFLAGS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2"
++ gl_COMPILER_OPTION_IF([-O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3], [
++ CFLAGS="$CFLAGS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3"
+ ],
+ [],
+ [AC_LANG_PROGRAM()]
+Index: dovecot/pigeonhole/m4/dovecot.m4
+===================================================================
+--- dovecot.orig/pigeonhole/m4/dovecot.m4
++++ dovecot/pigeonhole/m4/dovecot.m4
+@@ -9,7 +9,7 @@ dnl modifications, as long as this notic
+ # serial 41
+
+ dnl
+-dnl Check for support for D_FORTIFY_SOURCE=2
++dnl Check for support for D_FORTIFY_SOURCE=3
+ dnl
+
+ AC_DEFUN([AC_CC_D_FORTIFY_SOURCE],[
+@@ -17,8 +17,8 @@ AC_DEFUN([AC_CC_D_FORTIFY_SOURCE],[
+ AS_IF([test "$enable_hardening" = yes], [
+ case "$host" in
+ *)
+- gl_COMPILER_OPTION_IF([-O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2], [
+- CFLAGS="$CFLAGS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2"
++ gl_COMPILER_OPTION_IF([-O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3], [
++ CFLAGS="$CFLAGS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3"
+ ],
+ [],
+ [AC_LANG_PROGRAM()]
--- /dev/null
+From 003bf9a6959714e0f696f0015c8c712e89962b9b Mon Sep 17 00:00:00 2001
+From: Marco Bettini <marco.bettini@open-xchange.com>
+Date: Thu, 28 Aug 2025 15:09:56 +0000
+Subject: [PATCH] acl: Fix crash when group ACLs are used, but user's
+ acl_groups is empty
+Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1129952
+
+---
+ src/plugins/acl/acl-backend.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/plugins/acl/acl-backend.c b/src/plugins/acl/acl-backend.c
+index 2ad901cd96..cc08d702dd 100644
+--- a/src/plugins/acl/acl-backend.c
++++ b/src/plugins/acl/acl-backend.c
+@@ -155,7 +155,8 @@ bool acl_backend_user_name_equals(struct acl_backend *backend,
+ bool acl_backend_user_is_in_group(struct acl_backend *backend,
+ const char *group_name)
+ {
+- return array_bsearch(&backend->set->acl_groups, group_name,
++ return array_not_empty(&backend->set->acl_groups) &&
++ array_bsearch(&backend->set->acl_groups, group_name,
+ search_strcmp) != NULL;
+ }
+
+--
+2.47.3
+
--- /dev/null
+From e546239af593dfd0722bea9ff169b60006e219f9 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 15 May 2025 13:06:56 +0300
+Subject: [PATCH] auth: Terminate properly auth_oauth2_post_setting_defines
+ list
+
+Fixes:
+Error: xoauth2: oauth2 failed: Local validation failed: auth_oauth2_fields settings: Failed to parse configuration: settings struct auth_oauth2_fields #1 key mismatch
+---
+ src/auth/db-oauth2.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/auth/db-oauth2.c b/src/auth/db-oauth2.c
+index bd807e30b55..230c6cf7678 100644
+--- a/src/auth/db-oauth2.c
++++ b/src/auth/db-oauth2.c
+@@ -134,6 +134,8 @@ const struct setting_parser_info auth_oauth2_setting_parser_info = {
+ static const struct setting_define auth_oauth2_post_setting_defines[] = {
+ { .type = SET_STRLIST, .key = "oauth2_fields",
+ .offset = offsetof(struct auth_oauth2_post_settings, fields) },
++
++ SETTING_DEFINE_LIST_END
+ };
+
+ static const struct auth_oauth2_post_settings auth_oauth2_post_default_settings = {
--- /dev/null
+From a70ce7d3e2f983979e971414c5892c4e30197231 Mon Sep 17 00:00:00 2001
+From: Aki Tuomi <aki.tuomi@open-xchange.com>
+Date: Fri, 25 Jul 2025 08:16:52 +0300
+Subject: [PATCH] auth: Use AUTH_CACHE_KEY_USER instead of per-database
+ constants
+
+Fixes cache key issue where users would end up overwriting
+each other in cache due to cache key being essentially static
+string because we no longer support %u.
+
+Forgotten in 2e298e7ee98b6df61cf85117f000290d60a473b8
+---
+ src/auth/auth-settings.h | 2 ++
+ src/auth/passdb-bsdauth.c | 4 +---
+ src/auth/passdb-oauth2.c | 2 +-
+ src/auth/passdb-pam.c | 3 ++-
+ src/auth/passdb-passwd.c | 3 +--
+ src/auth/userdb-passwd.c | 3 +--
+ 6 files changed, 8 insertions(+), 9 deletions(-)
+
+diff --git a/src/auth/auth-settings.h b/src/auth/auth-settings.h
+index 1d420eceaaf..90aba17ec38 100644
+--- a/src/auth/auth-settings.h
++++ b/src/auth/auth-settings.h
+@@ -1,6 +1,8 @@
+ #ifndef AUTH_SETTINGS_H
+ #define AUTH_SETTINGS_H
+
++#define AUTH_CACHE_KEY_USER "%{user}"
++
+ struct master_service;
+ struct master_service_settings_output;
+
+diff --git a/src/auth/passdb-bsdauth.c b/src/auth/passdb-bsdauth.c
+index 68292679b7f..1b86da4053c 100644
+--- a/src/auth/passdb-bsdauth.c
++++ b/src/auth/passdb-bsdauth.c
+@@ -14,8 +14,6 @@
+ #include <login_cap.h>
+ #include <bsd_auth.h>
+
+-#define BSDAUTH_CACHE_KEY "%u"
+-
+ struct passdb_bsdauth_settings {
+ pool_t pool;
+ };
+@@ -104,7 +102,7 @@ bsdauth_preinit(pool_t pool, struct event *event,
+ &post_set, error_r) < 0)
+ return -1;
+ module->default_cache_key = auth_cache_parse_key_and_fields(
+- pool, BSDAUTH_CACHE_KEY, &post_set->fields, "bsdauth");
++ pool, AUTH_CACHE_KEY_USER, &post_set->fields, "bsdauth");
+
+ settings_free(post_set);
+ *module_r = module;
+diff --git a/src/auth/passdb-oauth2.c b/src/auth/passdb-oauth2.c
+index 96d902d323d..91fed060183 100644
+--- a/src/auth/passdb-oauth2.c
++++ b/src/auth/passdb-oauth2.c
+@@ -53,7 +53,7 @@ oauth2_preinit(pool_t pool, struct event *event, struct passdb_module **module_r
+ if (db_oauth2_init(event, TRUE, &module->db, error_r) < 0)
+ return -1;
+ module->module.default_pass_scheme = "PLAIN";
+- module->module.default_cache_key = "%u";
++ module->module.default_cache_key = AUTH_CACHE_KEY_USER;
+ *module_r = &module->module;
+ return 0;
+ }
+diff --git a/src/auth/passdb-pam.c b/src/auth/passdb-pam.c
+index 2acbceb80a3..fdf0f573ef4 100644
+--- a/src/auth/passdb-pam.c
++++ b/src/auth/passdb-pam.c
+@@ -415,7 +415,8 @@ static int pam_preinit(pool_t pool, struct event *event,
+ module = p_new(pool, struct pam_passdb_module, 1);
+ module->module.default_cache_key =
+ auth_cache_parse_key_and_fields(pool,
+- t_strdup_printf("%%u/%s", set->service_name),
++ t_strdup_printf("%"AUTH_CACHE_KEY_USER"\t%s",
++ set->service_name),
+ &post_set->fields, "pam");
+ module->requests_left = set->max_requests;
+ module->pam_setcred = set->setcred;
+diff --git a/src/auth/passdb-passwd.c b/src/auth/passdb-passwd.c
+index 13003151f9c..22e2eae7fa3 100644
+--- a/src/auth/passdb-passwd.c
++++ b/src/auth/passdb-passwd.c
+@@ -10,7 +10,6 @@
+ #include "safe-memset.h"
+ #include "ipwd.h"
+
+-#define PASSWD_CACHE_KEY "%u"
+ #define PASSWD_PASS_SCHEME "CRYPT"
+
+ #undef DEF
+@@ -142,7 +141,7 @@ static int passwd_preinit(pool_t pool, struct event *event,
+ &post_set, error_r) < 0)
+ return -1;
+ module->default_cache_key = auth_cache_parse_key_and_fields(pool,
+- PASSWD_CACHE_KEY,
++ AUTH_CACHE_KEY_USER,
+ &post_set->fields,
+ "passwd");
+ settings_free(post_set);
+diff --git a/src/auth/userdb-passwd.c b/src/auth/userdb-passwd.c
+index 5241129a0cc..14cf90a6d65 100644
+--- a/src/auth/userdb-passwd.c
++++ b/src/auth/userdb-passwd.c
+@@ -9,7 +9,6 @@
+ #include "ipwd.h"
+ #include "time-util.h"
+
+-#define USER_CACHE_KEY "%u"
+ #define PASSWD_SLOW_WARN_MSECS (10*1000)
+ #define PASSWD_SLOW_MASTER_WARN_MSECS 50
+ #define PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL 100
+@@ -225,7 +224,7 @@ static int passwd_preinit(pool_t pool, struct event *event ATTR_UNUSED,
+ struct passwd_userdb_module *module =
+ p_new(pool, struct passwd_userdb_module, 1);
+
+- module->module.default_cache_key = USER_CACHE_KEY;
++ module->module.default_cache_key = AUTH_CACHE_KEY_USER;
+ *module_r = &module->module;
+ return 0;
+ }
--- /dev/null
+Description: Fix GSSAPI regression
+ Dovecot 2.4 introduced a regression that broke GSSAPI authentication for
+ some clients. This patch contains a fix provided by the upstream maintainers.
+Origin: https://dovecot.org/mailman3/archives/list/dovecot@dovecot.org/message/O54EAGLIXXHMOH7BQCCKHHB3Z32HDWVR/
+Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1104549
+Last-Update: 2025-05-02
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+Index: dovecot/src/auth/mech-gssapi.c
+===================================================================
+--- dovecot.orig/src/auth/mech-gssapi.c
++++ dovecot/src/auth/mech-gssapi.c
+@@ -672,7 +672,7 @@ mech_gssapi_auth_initial(struct auth_req
+
+ if (data_size == 0) {
+ /* The client should go first */
+- auth_request_handler_reply_continue(request, NULL, 0);
++ auth_request_handler_reply_continue(request, uchar_empty_ptr, 0);
+ } else {
+ mech_gssapi_auth_continue(request, data, data_size);
+ }
--- /dev/null
+From 09cc148e4bbbae91514b6ff58bd71c15f95e4e8a Mon Sep 17 00:00:00 2001
+From: Jakob Haufe <sur5r@sur5r.net>
+Date: Sun, 25 May 2025 15:04:50 +0200
+Subject: [PATCH] Fix LDAP SASL auth support
+
+961275fdb54878fdfa4ee1b9f1a4f00e82bf4a83 moved code without creating a
+way to have HAVE_LDAP_SASL defined there.
+
+Copy the preprocessor block from src/auth/db-ldap.c to fix this.
+---
+ src/auth/db-ldap-settings.c | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+diff --git a/src/auth/db-ldap-settings.c b/src/auth/db-ldap-settings.c
+index 6c8feb334e..9245fcabd2 100644
+--- a/src/auth/db-ldap-settings.c
++++ b/src/auth/db-ldap-settings.c
+@@ -137,6 +137,18 @@ const struct setting_parser_info ldap_post_setting_parser_info = {
+
+ /* <settings checks> */
+
++#define HAVE_LDAP_SASL
++#ifdef HAVE_SASL_SASL_H
++# include <sasl/sasl.h>
++#elif defined (HAVE_SASL_H)
++# include <sasl.h>
++#else
++# undef HAVE_LDAP_SASL
++#endif
++#if !defined(SASL_VERSION_MAJOR) || SASL_VERSION_MAJOR < 2
++# undef HAVE_LDAP_SASL
++#endif
++
+ static int ldap_parse_deref(const char *str, int *ref_r)
+ {
+ if (strcasecmp(str, "never") == 0)
+--
+2.47.2
+
--- /dev/null
+From 576a2f52bff4c13971d9e6d1172857a4f18ddd14 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Thu, 6 Nov 2025 14:52:37 +0200
+Subject: [PATCH] auth: ldap - Fix crash if users are iterated, but
+ userdb_ldap_iterate_fields is not set
+Bug-Debian: https://bugs.debian.org/1121000
+---
+ src/auth/userdb-ldap.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/src/auth/userdb-ldap.c b/src/auth/userdb-ldap.c
+index 7d6d2bc38a..7aad2db74f 100644
+--- a/src/auth/userdb-ldap.c
++++ b/src/auth/userdb-ldap.c
+@@ -201,8 +201,11 @@ static void userdb_ldap_iterate_callback(struct ldap_connection *conn,
+ &set, &error) < 0) {
+ e_error(event, "%s", error);
+ ctx->ctx.failed = TRUE;
+- }
+- else {
++ } else if (!array_is_created(&set->iterate_fields)) {
++ e_error(event, "iterate: No userdb_ldap_iterate_fields specified");
++ ctx->ctx.failed = TRUE;
++ settings_free(set);
++ } else {
+ unsigned int count;
+ const char *const *items = array_get(&set->iterate_fields, &count);
+ for (unsigned int ndx = 0; ndx < count - 1;) {
--- /dev/null
+Index: dovecot/src/lib/test-event-filter.c
+===================================================================
+--- dovecot.orig/src/lib/test-event-filter.c
++++ dovecot/src/lib/test-event-filter.c
+@@ -1003,32 +1003,32 @@ static void test_event_filter_interval_v
+ { "field > 1mins", 60 * 1000 * 1000, FALSE },
+ { "field < 1mins", 60 * 1000 * 1000, FALSE },
+
+- { "field = 1hours", 60L * 60 * 1000 * 1000, TRUE },
+- { "field = 1h", 60L * 60 * 1000 * 1000, TRUE },
+- { "field = 3600000000", 60L * 60 * 1000 * 1000, TRUE },
+- { "field >= 1hours", 60L * 60 * 1000 * 1000, TRUE },
+- { "field <= 1hours", 60L * 60 * 1000 * 1000, TRUE },
+- { "field > 1mins", 60L * 60 * 1000 * 1000, TRUE },
+- { "field > 1hours", 60L * 60 * 1000 * 1000, FALSE },
+- { "field < 1hours", 60L * 60 * 1000 * 1000, FALSE },
++ { "field = 1hours", 60LL * 60 * 1000 * 1000, TRUE },
++ { "field = 1h", 60LL * 60 * 1000 * 1000, TRUE },
++ { "field = 3600000000", 60LL * 60 * 1000 * 1000, TRUE },
++ { "field >= 1hours", 60LL * 60 * 1000 * 1000, TRUE },
++ { "field <= 1hours", 60LL * 60 * 1000 * 1000, TRUE },
++ { "field > 1mins", 60LL * 60 * 1000 * 1000, TRUE },
++ { "field > 1hours", 60LL * 60 * 1000 * 1000, FALSE },
++ { "field < 1hours", 60LL * 60 * 1000 * 1000, FALSE },
+
+- { "field = 1days", 24L * 60 * 60 * 1000 * 1000, TRUE },
+- { "field = 1d", 24L * 60 * 60 * 1000 * 1000, TRUE },
+- { "field = 86400000000", 24L * 60 * 60 * 1000 * 1000, TRUE },
+- { "field >= 1days", 24L * 60 * 60 * 1000 * 1000, TRUE },
+- { "field <= 1days", 24L * 60 * 60 * 1000 * 1000, TRUE },
+- { "field > 1hours", 24L * 60 * 60 * 1000 * 1000, TRUE },
+- { "field > 1days", 24L * 60 * 60 * 1000 * 1000, FALSE },
+- { "field < 1days", 24L * 60 * 60 * 1000 * 1000, FALSE },
++ { "field = 1days", 24LL * 60 * 60 * 1000 * 1000, TRUE },
++ { "field = 1d", 24LL * 60 * 60 * 1000 * 1000, TRUE },
++ { "field = 86400000000", 24LL * 60 * 60 * 1000 * 1000, TRUE },
++ { "field >= 1days", 24LL * 60 * 60 * 1000 * 1000, TRUE },
++ { "field <= 1days", 24LL * 60 * 60 * 1000 * 1000, TRUE },
++ { "field > 1hours", 24LL * 60 * 60 * 1000 * 1000, TRUE },
++ { "field > 1days", 24LL * 60 * 60 * 1000 * 1000, FALSE },
++ { "field < 1days", 24LL * 60 * 60 * 1000 * 1000, FALSE },
+
+- { "field = 1weeks", 7L * 24 * 60 * 60 * 1000 * 1000, TRUE },
+- { "field = 1w", 7L * 24 * 60 * 60 * 1000 * 1000, TRUE },
+- { "field = 604800000000", 7L * 24 * 60 * 60 * 1000 * 1000, TRUE },
+- { "field >= 1weeks", 7L * 24 * 60 * 60 * 1000 * 1000, TRUE },
+- { "field <= 1weeks", 7L * 24 * 60 * 60 * 1000 * 1000, TRUE },
+- { "field > 1days", 7L * 24 * 60 * 60 * 1000 * 1000, TRUE },
+- { "field > 1weeks", 7L * 24 * 60 * 60 * 1000 * 1000, FALSE },
+- { "field < 1weeks", 7L * 24 * 60 * 60 * 1000 * 1000, FALSE },
++ { "field = 1weeks", 7LL * 24 * 60 * 60 * 1000 * 1000, TRUE },
++ { "field = 1w", 7LL * 24 * 60 * 60 * 1000 * 1000, TRUE },
++ { "field = 604800000000", 7LL * 24 * 60 * 60 * 1000 * 1000, TRUE },
++ { "field >= 1weeks", 7LL * 24 * 60 * 60 * 1000 * 1000, TRUE },
++ { "field <= 1weeks", 7LL * 24 * 60 * 60 * 1000 * 1000, TRUE },
++ { "field > 1days", 7LL * 24 * 60 * 60 * 1000 * 1000, TRUE },
++ { "field > 1weeks", 7LL * 24 * 60 * 60 * 1000 * 1000, FALSE },
++ { "field < 1weeks", 7LL * 24 * 60 * 60 * 1000 * 1000, FALSE },
+ };
+
+ struct event_filter *filter;
--- /dev/null
+Description: Fix groff errors in upstream manpages
+Author: Noah Meyerhans <noahm@debian.org>
+Forwarded: no
+Last-Update: 2025-05-02
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+Index: dovecot/doc/man/dovecot-lda.1
+===================================================================
+--- dovecot.orig/doc/man/dovecot-lda.1
++++ dovecot/doc/man/dovecot-lda.1
+@@ -1,5 +1,6 @@
+-.TH "" "" "March 2025" "78ffb79" "Dovecot"
+-dovecot-lda - Dovecot's local mail delivery agent
++.TH "DOVECOT-LDA" "1" "March 2025" "78ffb79" "Dovecot"
++.SH "NAME"
++\fBdovecot-lda\fR - Dovecot's local mail delivery agent
+ .SH "SYNOPSIS"
+ .P
+ \fBdovecot-lda\fR \[lB]\fB-ek\fR\[rB] \[lB]\fB-a\fR \fIaddress\fR\[rB] \[lB]\fB-c\fR \fIconfig_file\fR\[rB] \[lB]\fB-d\fR \fIusername\fR\[rB] \[lB]\fB-f\fR \fIenvelope_sender\fR\[rB] \[lB]\fB-m\fR \fImailbox\fR\[rB] \[lB]\fB-o\fR \fIsetting=value\fR\[rB] \[lB]\fB-p\fR \fIpath\fR\[rB] \[lB]\fB-r\fR \fIaddress\fR\[rB]
+Index: dovecot/doc/man/doveadm-deduplicate.1
+===================================================================
+--- dovecot.orig/doc/man/doveadm-deduplicate.1
++++ dovecot/doc/man/doveadm-deduplicate.1
+@@ -6,7 +6,7 @@
+ \fBdoveadm\fR \[lB]\fIGLOBAL OPTIONS\fR\[rB] \fBdeduplicate\fR \[lB]\fB-u\fR \fIuser\fR | \fB-A\fR | \fB-F\fR \fIfile\fR | \fB--no-userdb-lookup\fR\[rB] \[lB]\fB-S\fR \fIsocket_path\fR\[rB] \[lB]\fB-m\fR\[rB] \fIsearch_query\fR
+ .SH "DESCRIPTION"
+ .P
+-. doveadm(1) will delete the newest duplicated messages from the mailbox and keep the oldest.
++doveadm(1) will delete the newest duplicated messages from the mailbox and keep the oldest.
+ .P
+ Deduplication across multiple mailboxes is not supported.
+ .SH "GLOBAL OPTIONS"
+@@ -157,7 +157,7 @@ Run the \fIcommand\fR only for the given
+ .RS 0
+ .RS 4
+ .P
+-' will be sufficient. See doveadm-search-query(7) for details.
++will be sufficient. See doveadm-search-query(7) for details.
+ .RE 0
+
+ .RE 0
+@@ -179,7 +179,7 @@ guid uid
+ 8aad0f0a30169f4bea620000ca356bad 18756
+ 923e301ab9219b4b4f440000ca356bad 18748
+ 923e301ab9219b4b4f440000ca356bad 18753
+-...
++\fR...
+ .fi
+ .RE
+ .P
+@@ -196,7 +196,7 @@ guid uid
+ 8aad0f0a30169f4bea620000ca356bad 18751
+ 923e301ab9219b4b4f440000ca356bad 18748
+ a7999e1530739c4bd26d0000ca356bad 18749
+-...
++\fR...
+ .fi
+ .RE
+ .SH "REPORTING BUGS"
+Index: dovecot/doc/man/doveadm-fetch.1
+===================================================================
+--- dovecot.orig/doc/man/doveadm-fetch.1
++++ dovecot/doc/man/doveadm-fetch.1
+@@ -377,7 +377,7 @@ The date-time attribute when present, ot
+ .RS 0
+ .RS 4
+ .P
+-Fetch messages matching this search query. See doveadm-search-query(1) for details.
++Fetch messages matching this search query. See doveadm-search-query(7) for details.
+ .RE 0
+
+ .RE 0
--- /dev/null
+From 3ff6022baa757583faf579251fc4033f373d4244 Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Tue, 2 Sep 2025 13:28:44 +0300
+Subject: [PATCH] lib-mail: istream-header-filter - Fix potential assert-crash
+
+In some situations multiple snapshots were created, which broke the logic
+of using a single snapshot_pending boolean. Changed it to refcount.
+
+Fixes:
+Panic: file istream-header-filter.c: line 665 (i_stream_header_filter_snapshot_free): assertion failed: (snapshot->mstream->snapshot_pending)
+---
+ src/lib-mail/istream-header-filter.c | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/src/lib-mail/istream-header-filter.c b/src/lib-mail/istream-header-filter.c
+index ee94e4f82b..2d938bdaad 100644
+--- a/src/lib-mail/istream-header-filter.c
++++ b/src/lib-mail/istream-header-filter.c
+@@ -27,6 +27,7 @@ struct header_filter_istream {
+ void *context;
+
+ buffer_t *hdr_buf;
++ int snapshot_pending_refcount;
+ struct message_size header_size;
+ uoff_t skip_count;
+ uoff_t last_lf_offset;
+@@ -50,7 +51,6 @@ struct header_filter_istream {
+ bool eoh_not_matched:1;
+ bool callbacks_called:1;
+ bool prev_matched:1;
+- bool snapshot_pending:1;
+ };
+
+ header_filter_callback *null_header_filter_callback = NULL;
+@@ -66,7 +66,7 @@ static void i_stream_header_filter_destroy(struct iostream_private *stream)
+ message_parse_header_deinit(&mstream->hdr_ctx);
+ if (array_is_created(&mstream->match_change_lines))
+ array_free(&mstream->match_change_lines);
+- if (!mstream->snapshot_pending)
++ if (mstream->snapshot_pending_refcount == 0)
+ buffer_free(&mstream->hdr_buf);
+ else {
+ /* Clear hdr_buf to make sure
+@@ -168,7 +168,7 @@ static ssize_t hdr_stream_update_pos(struct header_filter_istream *mstream)
+
+ static void hdr_buf_realloc_if_needed(struct header_filter_istream *mstream)
+ {
+- if (!mstream->snapshot_pending)
++ if (mstream->snapshot_pending_refcount == 0)
+ return;
+
+ /* hdr_buf exists in a snapshot. Leave it be and create a copy of it
+@@ -177,7 +177,7 @@ static void hdr_buf_realloc_if_needed(struct header_filter_istream *mstream)
+ mstream->hdr_buf = buffer_create_dynamic(default_pool,
+ I_MAX(1024, old_buf->used));
+ buffer_append(mstream->hdr_buf, old_buf->data, old_buf->used);
+- mstream->snapshot_pending = FALSE;
++ mstream->snapshot_pending_refcount = 0;
+
+ mstream->istream.buffer = mstream->hdr_buf->data;
+ }
+@@ -662,8 +662,8 @@ i_stream_header_filter_snapshot_free(struct istream_snapshot *_snapshot)
+ if (snapshot->mstream->hdr_buf != snapshot->hdr_buf)
+ buffer_free(&snapshot->hdr_buf);
+ else {
+- i_assert(snapshot->mstream->snapshot_pending);
+- snapshot->mstream->snapshot_pending = FALSE;
++ i_assert(snapshot->mstream->snapshot_pending_refcount > 0);
++ snapshot->mstream->snapshot_pending_refcount--;
+ }
+ i_free(snapshot);
+ }
+@@ -687,7 +687,7 @@ i_stream_header_filter_snapshot(struct istream_private *stream,
+ snapshot->hdr_buf = mstream->hdr_buf;
+ snapshot->snapshot.free = i_stream_header_filter_snapshot_free;
+ snapshot->snapshot.prev_snapshot = prev_snapshot;
+- mstream->snapshot_pending = TRUE;
++ mstream->snapshot_pending_refcount++;
+ return &snapshot->snapshot;
+ }
+
+--
+2.47.3
+
--- /dev/null
+From 2b9a4e85bc4547fcff84398b2bda00d0ced09fae Mon Sep 17 00:00:00 2001
+From: Alexander Gerasiov <a@gerasiov.net>
+Date: Tue, 23 Sep 2025 13:50:43 +0300
+Subject: [PATCH] lib-sieve/sieve-script.c: sieve_script_create_common:
+ Correctly handle errors.
+
+Fixes null pointer deref (e.g. in case of absent file).
+---
+ src/lib-sieve/sieve-script.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+Index: dovecot/pigeonhole/src/lib-sieve/sieve-script.c
+===================================================================
+--- dovecot.orig/pigeonhole/src/lib-sieve/sieve-script.c
++++ dovecot/pigeonhole/src/lib-sieve/sieve-script.c
+@@ -166,8 +166,8 @@ sieve_script_create_common(struct sieve_
+ sieve_storage_unref(&storage);
+ if (ret > 0 && open &&
+ sieve_script_open(script, error_code_r) < 0) {
+- *error_r = sieve_storage_get_last_error(
+- storage, error_code_r);
++ *error_r = sieve_script_get_last_error(
++ script, error_code_r);
+ sieve_script_unref(&script);
+ if (*error_code_r == SIEVE_ERROR_NOT_FOUND)
+ continue;
--- /dev/null
+#split-protocols.patch
+#fix-mail_plugin_dir-default.patch
+#ssl-cert-location.patch
+#tcpwrapper.patch
+#default-mail_location.patch
+#mboxlocking.patch
+#dovecot_name.patch
+#ssl-dh-params-location.patch
+skip-rfc-subdir.patch
+#Correct-misspellings.patch
+#test-backtrace.patch
+#doveadm-director.1-drop-acute-accent.patch
+#Fix-32bit-sign-comparisons.patch
+#Improve-cross-compile-support.patch
+#Silence-LTO-related-compiler-warning.patch
+#md4-md5-disable-optimization-causing-unaligned-access.patch
+#Support-openssl-3.0.patch
+#Debug-flaky-unit-test.patch
+#auth-Fix-handling-passdbs-with-identical-driver-args-but-.patch
+#auth-Add-a-comment-about-updating-userdb_find.patch
+#Fix-uninitialized-read-in-doveadm-oldstats.patch
+0001-lda-Fix-using-USER-environment-if-d-hasn-t-been-spec.patch
+0002-lda-Default-mail_home-HOME-environment-if-not-using-.patch
+Use-_FORTIFY_SOURCE-level-3.patch
+fit-32-bit-test-integers.patch
+bug1104549-gssapi-regression.patch
+fix-man-errors.patch
+bug1106784_Fix-LDAP-SASL-auth-support.patch
+auth__Use_AUTH_CACHE_KEY_USER_instead_of_per-database.patch
+auth__Terminate_properly_auth_oauth2_post_setting_defines.patch
+lib-sieve_sieve-script_c_sieve_script_create_common_Correctly_handle_errors.patch
+bug1121000_dovecot-ldap_Crash_if_iterate_filter_is_set_but_iterate_fields_is_not_set.patch
+0001-trash-Use-mailbox-event-in-trash_try_mailbox-for-set.patch
+acl-Fix-crash-when-group-ACLs-are-used-but-user-s-ac.patch
+CVE-2025-59028.patch
+CVE-2025-59031.patch
+CVE-2025-59032.patch
+CVE-2026-24031-27860-1.patch
+CVE-2026-24031-27860-2.patch
+CVE-2026-24031-27860-3.patch
+CVE-2026-24031-27860-4.patch
+CVE-2026-24031-27860-5.patch
+CVE-2026-24031-27860-6.patch
+CVE-2026-24031-27860-7.patch
+CVE-2026-24031-27860-8.patch
+CVE-2026-27855-1.patch
+CVE-2026-27855-2.patch
+CVE-2026-27855-3.patch
+CVE-2026-27855-4.patch
+CVE-2026-27856-1.patch
+CVE-2026-27856-2.patch
+CVE-2026-27856-3.patch
+CVE-2026-27857-1.patch
+CVE-2026-27857-2.patch
+CVE-2026-27857-3.patch
+CVE-2026-27857-4.patch
+CVE-2026-27857-5.patch
+CVE-2026-27858.patch
+CVE-2026-27859.patch
+CVE-2026-27851.patch
+CVE-2026-40016.patch
+CVE-2026-33603.patch
+CVE-2026-40020-1.patch
+CVE-2026-40020-2.patch
+CVE-2026-40020-3.patch
+CVE-2026-42006.patch
+lib-mail-istream-header-filter-Fix-potential-assert-.patch
+# CVE-2026-52687
+0001-imap-Restrict-COMPRESS-to-DEFLATE-only.patch
+0002-doveadm-compress-connect-Don-t-hang-when-server-reje.patch
+0003-doveadm-compress-Delay-failing-if-used-COMPRESS-algo.patch
+# CVE-2026-52681
+0001-lib-storage-Allow-NULL-path-in-mailbox_list_mkdir_ro.patch
+0002-lib-storage-Simplify-mailbox_mkdir-using-mailbox_lis.patch
+0001-lib-sieve-sieve-storage-Add-is_personal-bit.patch
+0001-lib-sieve-sieve-storage-Fix-panic-caused-by-flawed-d.patch
+0001-lib-sieve-Add-per-user-sieve-rusage-file-for-cumulat.patch
+0002-lib-sieve-Drop-binary-header-resource-usage-tracking.patch
+0003-lib-sieve-Rename-on-disk-header-rusage-fields-to-unu.patch
+0004-lib-sieve-Switch-sieve_max_cpu_time-enforcement-to-t.patch
+0005-lib-sieve-file-storage-Remove-orphan-compiled-binari.patch
+0006-lib-sieve-Log-a-warning-when-cumulative-CPU-exceeds-.patch
+0007-lib-sieve-Log-an-info-line-when-opening-a-CPU-disabl.patch
+0008-lib-sieve-Reduce-CPU-limit-check-interval-in-contain.patch
+# CVE-2026-42391
+0001-imap-login-Add-comments-to-internal-ID-command-param.patch
+0001-imap-login-Fix-excessive-memory-growth-with-pre-logi.patch
+# CVE-2026-40204
+0001-lib-lda-mail-deliver-Autocreate-delivery-mailbox-via.patch
+0002-lib-storage-Remove-MAILBOX_FLAG_AUTO_CREATE-and-MAIL.patch
+0001-lib-sieve-actions-Split-mailbox-creation-into-sieve_.patch
+0002-lib-sieve-actions-Create-fileinto-mailbox-without-th.patch
+# CVE-2026-42394
+0001-lib-i_close_fd-Document-that-it-preserves-errno.patch
+0002-lib-path-util-Add-t_openat_safe-for-jailed-openat-re.patch
+0003-lib-Add-t_openat_safe_dir.patch
+0004-acl-dovecot-acl-list-is-never-supposed-to-be-a-symli.patch
+0005-lib-Add-comments-about-memory-allocations-and-string.patch
+0001-lib-sieve-storage-file-Refuse-symlinks-escaping-pers.patch
+0002-lib-sieve-storage-file-Validate-script-stat-path-thr.patch
+# CVE-2026-73209
+0004-lib-compression-test-Add-zero-len-frame-test-for-all.patch
+0003-lib-compression-istream-zstd-Guard-against-no-progre.patch
+0001-lib-compression-istream-lz4-Ensure-uncompressed-chun.patch
+0001-lib-compression-istream-lz4-Try-again-if-no-data-was.patch
+0001-lib-compression-Don-t-calculate-crc32-if-using-zlib-.patch
+0001-lib-compression-istream-zlib-Use-container_of-macro.patch
+0001-global-Fix-spelling.patch
+0001-lib-compression-wrap-_read-in-for-loop-reindent-only.patch
+0002-lib-compression-Iterate-instead-of-recursing-on-zero.patch
+# CVE-2026-40018
+0001-lib-sql-sql-api-Implement-async-calls-for-drivers-th.patch
+0001-lib-sql-Add-sql_result_new_error-helper.patch
+0002-lib-sql-Extract-sql_query_callback_delayed-helper.patch
+0003-lib-sql-Add-sql_commit_schedule_delayed-helper.patch
+0004-lib-var-expand-Change-escape-func-signature-to-retur.patch
+0001-lib-sql-driver-sqlite-Use-sqlite3_snprintf-to-quote-.patch
+0001-lib-sql-Use-strchr-based-while-loop-to-fill-template.patch
+0005-lib-sql-Make-escape_string-return-int-with-error_r-f.patch
+0006-auth-passdb_sql-connect-before-expanding-query-varia.patch
+0007-lib-sql-Run-sql-statement-queries-in-their-own-data-.patch
+# CVE-2026-40205 and CVE-2026-73208
+0001-auth-db-oauth2-Reduce-nesting-in-token_in_scope-via-.patch
+0002-auth-db-oauth2-Fix-scope-check-to-require-all-config.patch
+0003-auth-db-oauth2-Add-oauth2_audience-setting-deprecate.patch
+# CVE-2026-33604
+0001-lib-mail-ostream-dot-Optimize-stream-writing.patch
+0001-lib-mail-o_stream_dot_sendv-Use-I_MIN.patch
+0002-lib-mail-o_stream_dot_sendv-Fix-boundary-off-by-one.patch
+0003-lib-mail-o_stream_dot_sendv-Use-enumeration-to-defin.patch
+0001-lib-test-Add-test_assert_memcmp.patch
+0001-lib-test-Require-both-lengths-in-test_assert_memcmp.patch
+0004-lib-mail-o_stream_dot_sendv-Do-not-send-unguarded-.-.patch
+# CVE-2026-33607
+0001-lib-imap-imap-match-Fix-excessive-CPU-usage-caused-b.patch
+# CVE-2026-27852
+0001-lib-mail-Reset-charset-translation-buffer-between-MI.patch
+0002-imap-Stream-BODYSTRUCTURE-to-client-without-ostream-.patch
+0003-imap-Stream-ENVELOPE-to-client-without-ostream-memor.patch
+0004-lib-mail-message-parser-from-parts-Enforce-50-MB-all.patch
+0005-lib-mail-Add-count-field-to-struct-message_address_l.patch
+0006-lib-storage-mbox-Explicitly-disable-the-header-block.patch
+0007-lib-mail-istream-header-filter-Use-container_of-for-.patch
+0008-lib-mail-Make-istream-header-filter-s-per-header-siz.patch
+0009-lib-storage-Cap-per-header-size-in-index_mail_get_he.patch
+0010-lib-mail-Clamp-hdr-value-cumulatively-against-header.patch
+0011-lib-mail-Introduce-struct-message_part_data_limits-a.patch
+0012-lib-mail-Limit-total-address-count-per-message-to-10.patch
+0013-lib-mail-Limit-total-Content-Language-tag-count-per-.patch
+0014-lib-mail-Limit-total-MIME-parameter-count-per-messag.patch
+# CVE-2026-40014
+0001-lib-storage-thread-Avoid-excessive-data-stack-growth.patch
+0002-lib-storage-thread-Limit-References-header-msgid-cou.patch
+0003-lib-storage-thread-Limit-ancestor-chain-traversal-de.patch
+# CVE-2026-40013
+0001-lib-sieve-sieve-binary-code-Fix-single-byte-oob-stac.patch
+# CVE-2026-33605
+0001-lib-managesieve-managesieve-parser-Fix-handling-of-l.patch
+0002-managesieve-login-client_skip_line-Discard-data-when.patch
+# CVE-2026-40015
+0001-imap-hibernate-Fix-out-of-bounds-read-when-parsing-D.patch
+0002-imap-hibernate-Use-imap-parser-API-for-parsing-DONE-.patch
+# CVE-2026-40017
+0001-lib-Add-XXH64-hash-implementation.patch
+0001-lib-xxh64-fix-byte-ordering-bug-on-big-endian-system.patch
+0002-lib-Replace-str_hash-strcase_hash-with-xxh64.patch
+0003-lib-Add-str_stable_hash.patch
+0004-lib-Key-str_hash-strcase_hash-with-a-random-seed-to-.patch
+0005-lib-index-mail-index-strmap-Fix-OOB-read-from-trunca.patch
+0006-lib-index-Rename-crc32-variables-parameters-to-hash-.patch
+0007-lib-index-Add-keyed-xxh64-strmap-format-v2-gated-by-.patch
+0008-doveadm-Fix-ubsan-unsigned-integer-overflow-error.patch
+0009-config-Assume-dovecot_config_version-0.0.0-is-the-sa.patch
+0010-lib-master-Move-config_version_find-from-src-config-.patch
+0011-lib-lib-master-Move-version_-to-lib-version.-ch.patch
+0012-config-doveconf-dF-Add-dovecot_storage_version.patch
+# CVE-2026-42007
+0001-lib-sieve-util-edit-mail-Fix-writing-to-freed-memory.patch
+0002-lib-sieve-util-test-edit-mail-Adjust-to-changes-in-d.patch
+0003-lib-sieve-edit_mail_headers_parse-Fix-info-leak-via-.patch
+# CVE-2026-33263
+0001-login-common-client-common-Add-client_disconnect-vfu.patch
+0002-submission-login-client-Fix-panic-occurring-at-mail_.patch
+0003-lib-smtp-smtp-server-Expose-smtp_server_connection_r.patch
+0004-submission-login-client-authenticate-Reply-421-4.7.0.patch
+0001-commin-Rename-LOGIN_PROXY_FAILURE_TYPE_AUTH-to-LOGIN.patch
+0001-managesieve-login-Adjust-to-core-s-login_proxy_failu.patch
+0001-login-common-login-Add-proxy_dest_connection_limit-e.patch
+0005-submission-login-submission-proxy-Recognize-421-4.7..patch
+0001-managesieve-login-Support-LOGIN_PROXY_FAILURE_TYPE_A.patch
+0001-login-Add-LOGIN_PROXY_FAILURE_TYPE_AUTH_NOT_REPLIED.patch
+# CVE-2026-33606
+0001-dsync-Avoid-potentially-excessive-data-stack-growth-.patch
+0002-dsync-Fix-escaping-mail-or-attribute-value-that-begi.patch
+# CVE-2026-40203
+0001-imap-Extract-side-channel-ostream-creation-into-help.patch
+0002-imap-Recreate-multiplex-ostream-side-channel-after-u.patch
+0003-lib-compression-Add-o_stream_deflate_reset_dict.patch
+0004-imap-login-Add-dict_reset-side-channel-command-to-im.patch
+# CVE-2026-42008
+0005-imap-Add-imap_compress_on_proxy-hidden-setting.patch
+0006-imap-Reset-DEFLATE-dictionary-after-every-tagged-rep.patch
+0001-submission-Fix-settings-leak-on-error-paths-in-clien.patch
+0002-auth-Fix-prefixing-forward_fields-without-a-value-fr.patch
+# CVE-2026-42392
+0001-lib-imap-urlauth-Fix-leaking-uninitialized-memory-in.patch
+# CVE-2026-42393
+0001-lib-Add-str_equals_timing_safe.patch
+0002-doveadm-Avoid-leaking-doveadm_password-or-doveadm_ap.patch
+# CVE-2026-42395
+0001-login-common-Fix-crash-when-XCLIENT-FORWARD-base64-c.patch
+0002-login-common-Avoid-array_front-panic-on-empty-forwar.patch
--- /dev/null
+From: Noah Meyerhans <noahm@debian.org>
+Date: Thu, 21 May 2020 21:48:59 -0700
+Subject: Don't try to build doc/rfc subdir components
+Forwarded: not-needed
+---
+ pigeonhole/configure.ac | 1 -
+ pigeonhole/doc/Makefile.am | 1 -
+ pigeonhole/doc/Makefile.in | 1 -
+ 3 files changed, 3 deletions(-)
+
+Index: dovecot/pigeonhole/configure.ac
+===================================================================
+--- dovecot.orig/pigeonhole/configure.ac
++++ dovecot/pigeonhole/configure.ac
+@@ -258,7 +258,6 @@ doc/Makefile
+ doc/man/Makefile
+ doc/example-config/Makefile
+ doc/example-config/conf.d/Makefile
+-doc/rfc/Makefile
+ doc/extensions/Makefile
+ doc/locations/Makefile
+ doc/plugins/Makefile
+Index: dovecot/pigeonhole/doc/Makefile.am
+===================================================================
+--- dovecot.orig/pigeonhole/doc/Makefile.am
++++ dovecot/pigeonhole/doc/Makefile.am
+@@ -1,7 +1,6 @@
+ SUBDIRS = \
+ man \
+ example-config \
+- rfc \
+ extensions \
+ locations \
+ plugins
--- /dev/null
+# Dutch translation of dovecot debconf templates.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the dovecot package.
+# Frans Spiesschaert <Frans.Spiesschaert@yucom.be>, 2014.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: dovecot\n"
+"Report-Msgid-Bugs-To: dovecot@packages.debian.org\n"
+"POT-Creation-Date: 2013-11-07 07:06+0100\n"
+"PO-Revision-Date: 2014-10-04 22:25+0200\n"
+"Last-Translator: Frans Spiesschaert <Frans.Spiesschaert@yucom.be>\n"
+"Language-Team: Debian Dutch l10n Team <debian-l10n-dutch@lists.debian.org>\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. Type: error
+#. Description
+#: ../dovecot-core.templates:2001
+msgid "Wrong location for SSL certificates"
+msgstr "De SSL-certificaten staan op een verkeerde plaats"
+
+#. Type: error
+#. Description
+#: ../dovecot-core.templates:2001
+msgid ""
+"This machine uses SSL certificates for Dovecot. These certificates should be "
+"moved from /etc/ssl to /etc/dovecot and Dovecot's configuration file (/etc/"
+"dovecot/conf.d/10-ssl.conf) should be updated accordingly."
+msgstr ""
+"Deze computer gebruikt SSL-certificaten voor Dovecot. Deze certificaten "
+"moeten verplaatst worden van /etc/ssl naar /etc/dovecot en het "
+"configuratiebestand van Dovecot (/etc/dovecot/conf.d/10-ssl.conf) moet in "
+"die zin aangepast worden."
+
+#. Type: error
+#. Description
+#: ../dovecot-core.templates:2001
+msgid "Please read /usr/share/doc/dovecot-core/README.Debian.gz for details."
+msgstr ""
+"Raadpleeg /usr/share/doc/dovecot-core/README.Debian.gz voor de details."
+
+#. Type: boolean
+#. Description
+#: ../dovecot-core.templates:3001
+msgid "Create a self-signed SSL certificate?"
+msgstr "Een door uzelf ondertekend SSL-certificaat aanmaken?"
+
+#. Type: boolean
+#. Description
+#: ../dovecot-core.templates:3001
+msgid ""
+"An SSL certificate is needed in order to use IMAP or POP3 over SSL/TLS. No "
+"such certificate was found."
+msgstr ""
+"U heeft een SSL-certificaat nodig om IMAP of POP3 over SSL/TLS te kunnen "
+"gebruiken. Een dergelijk certificaat werd niet gevonden."
+
+#. Type: boolean
+#. Description
+#: ../dovecot-core.templates:3001
+msgid ""
+"Please choose whether you want to create one now. This will then be a self-"
+"signed certificate."
+msgstr ""
+"Geef aan of u nu een dergelijk certificaat wilt aanmaken. In dat geval zal "
+"het een door uzelf ondertekend certificaat zijn."
+
+#. Type: boolean
+#. Description
+#: ../dovecot-core.templates:3001
+msgid ""
+"If you choose not to create a certificate, please adapt Dovecot's "
+"configuration file (/etc/dovecot/conf.d/10-ssl.conf)."
+msgstr ""
+"Indien u ervoor kiest om geen certificaat aan te maken, dan moet u het "
+"configuratiebestand van Dovecot (/etc/dovecot/conf.d/10-ssl.conf) wijzigen."
+
+#. Type: string
+#. Description
+#: ../dovecot-core.templates:4001
+msgid "Host name:"
+msgstr "Computernaam"
+
+#. Type: string
+#. Description
+#: ../dovecot-core.templates:4001
+msgid "Please enter the host name to use in the SSL certificate."
+msgstr ""
+"Geef aan welke computernaam gebruikt moet worden in het SSL-certificaat."
+
+#. Type: string
+#. Description
+#: ../dovecot-core.templates:4001
+msgid ""
+"It will become the \"commonName\" field of the generated SSL certificate."
+msgstr ""
+"Die komt terecht in het veld \"commonName\" van het SSL-certificaat dat "
+"aangemaakt wordt."
--- /dev/null
+#!/usr/bin/make -f
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+SHELL=/bin/bash -O extglob
+PIGEONHOLE_DIR=$(CURDIR)/pigeonhole
+
+export DEB_BUILD_MAINT_OPTIONS=hardening=+all optimize=+lto
+
+# LP: 1636781 - strip incompatible default linker option
+ifeq ($(shell dpkg-vendor --derives-from Ubuntu && echo yes),yes)
+ export DEB_LDFLAGS_MAINT_STRIP = -Wl,-Bsymbolic-functions
+endif
+
+DOV_DEB_CFLAGS=
+DOV_DEB_CXXFLAGS=
+DOV_DEB_LDFLAGS=
+
+# Ensure that stacktrace generation works:
+DOV_DEB_LDFLAGS += -rdynamic
+include /usr/share/dpkg/architecture.mk
+ifneq ($(filter armel armhf hppa mips64el mipsel riscv64 sparc64, $(DEB_HOST_ARCH)),)
+ DOV_DEB_CFLAGS += -funwind-tables
+ DOV_DEB_CXXFLAGS += -funwind-tables
+endif
+
+# Extra hardening flags, not for hppa.
+ifeq ($(filter hppa, $(DEB_HOST_ARCH)),)
+ DOV_DEB_CFLAGS += -fstack-clash-protection
+ DOV_DEB_CXXFLAGS += -fstack-clash-protection
+ DOV_DEB_LDFLAGS += -fstack-clash-protection
+endif
+
+export DEB_CFLAGS_MAINT_APPEND = $(DOV_DEB_CFLAGS)
+export DEB_CXXFLAGS_MAINT_APPEND = $(DOV_DEB_CXXFLAGS)
+export DEB_LDFLAGS_MAINT_APPEND = $(DOV_DEB_LDFLAGS)
+
+
+ifeq ($(DEB_HOST_ARCH_OS),linux)
+ CONFIGURE_APPARMOR = --with-apparmor
+endif
+
+ifeq (i386,$(DEB_HOST_ARCH))
+ CONFIGURE_Y2038=--disable-year2038
+else
+ CONFIGURE_Y2038=--enable-year2038
+endif
+
+CLEANFILES=src/lib-settings/settings-history-core.c \
+ src/lib-dict-backend/dict-drivers-register.c \
+ src/lib-sql/test-database.db \
+ pigeonhole/src/plugins/settings/pigeonhole-settings-dynamic.c
+
+# Macros to help move files to the module packages
+CORE_DIR=$(CURDIR)/debian/dovecot-core
+PKG_DIR=$(CURDIR)/debian/dovecot-PKG
+MOVE=install -d $(PKG_DIR:PKG=$(2))/$(dir $(1)); mv $(CORE_DIR)/$(1) $(PKG_DIR:PKG=$(2))/$(dir $(1));
+
+# Files to move from dovecot-core to the individual packages. Files added to
+# <addon>_files will be automatically moved from dovecot's tree to the
+# respective package.
+auth-lua_files = usr/lib/dovecot/modules/auth/libauthdb_lua.so
+
+imapd_files = usr/lib/dovecot/imap* \
+ usr/lib/dovecot/modules/lib??_imap_!(*sieve*) \
+ usr/share/dovecot/conf.d/??-imap*
+
+pop3d_files = usr/lib/dovecot/pop3* \
+ usr/share/dovecot/conf.d/??-pop3*
+
+lmtpd_files = usr/lib/dovecot/lmtp* \
+ usr/share/dovecot/conf.d/??-lmtp*
+
+managesieved_files = usr/lib/dovecot/managesieve* \
+ usr/lib/dovecot/modules/settings/libmanagesieve* \
+ usr/share/dovecot/conf.d/??-managesieve.conf
+
+submissiond_files = usr/lib/dovecot/submission* \
+ usr/share/dovecot/conf.d/??-submission.conf
+
+pgsql_files = usr/lib/dovecot/modules/auth/libdriver_pgsql* \
+ usr/lib/dovecot/modules/dict/libdriver_pgsql* \
+ usr/lib/dovecot/modules/libdriver_pgsql*
+
+mysql_files = usr/lib/dovecot/modules/auth/libdriver_mysql* \
+ usr/lib/dovecot/modules/dict/libdriver_mysql* \
+ usr/lib/dovecot/modules/libdriver_mysql*
+
+sqlite_files = usr/lib/dovecot/modules/auth/libdriver_sqlite* \
+ usr/lib/dovecot/modules/dict/libdriver_sqlite* \
+ usr/lib/dovecot/modules/libdriver_sqlite*
+
+gssapi_files = usr/lib/dovecot/modules/auth/libmech_gssapi*
+
+ldap_files = usr/lib/dovecot/modules/auth/libauthdb_ldap* \
+ usr/lib/dovecot/modules/dict/libdict_ldap* \
+ usr/lib/dovecot/libdovecot-ldap* \
+ usr/share/dovecot/*-ldap.conf.ext \
+ usr/share/dovecot/conf.d/*-ldap.conf.ext
+
+sieve_files = usr/bin/sieve* \
+ usr/lib/dovecot/modules/lib??_sieve* \
+ usr/lib/dovecot/modules/lib??_imap_*sieve* \
+ usr/lib/dovecot/modules/doveadm/lib??_doveadm_sieve* \
+ usr/lib/dovecot/modules/sieve \
+ usr/lib/dovecot/libdovecot-sieve* \
+ usr/share/man/man1/sieve* \
+ usr/share/dovecot/conf.d/??-sieve.conf \
+ usr/share/dovecot/conf.d/??-sieve-extprograms.conf
+
+flatcurve_files = usr/lib/dovecot/modules/lib??_fts_flatcurve_* \
+ usr/share/dovecot/conf.d/??-fts-flatcurve.conf
+
+solr_files = usr/lib/dovecot/modules/lib??_fts_solr_*
+
+dev_files = usr/include/* \
+ usr/lib/dovecot/dovecot-config \
+ usr/share/aclocal/*.m4
+
+%:
+ dh $@
+
+override_dh_autoreconf:
+ dh_autoreconf debian/autogen.sh
+
+override_dh_auto_configure: TEST_DIR_OVERRIDE=$(shell mktemp -d)
+override_dh_auto_configure:
+ TEST_DIR_OVERRIDE='$(TEST_DIR_OVERRIDE)' KRB5CONFIG=krb5-config.mit systemdsystemunitdir=/usr/lib/systemd/system dh_auto_configure -- \
+ --enable-experimental-mail-utf8 \
+ --with-ldap=plugin \
+ --with-ssl=openssl \
+ --with-sql=plugin \
+ --with-pgsql \
+ --with-mysql \
+ --with-sqlite \
+ --with-gssapi=plugin \
+ --with-solr \
+ --with-flatcurve \
+ --with-ioloop=best \
+ --with-icu \
+ $(CONFIGURE_APPARMOR) \
+ --with-lz4 \
+ --with-lua=plugin \
+ --with-sodium \
+ --libdir=\$${prefix}/lib \
+ --libexecdir=\$${prefix}/lib \
+ --docdir=\$${prefix}/share/doc/dovecot-core \
+ --with-moduledir=\$${prefix}/lib/dovecot/modules \
+ --with-rundir=/run/dovecot \
+ --disable-rpath \
+ --disable-static \
+ $(CONFIGURE_Y2038)
+
+ifeq (i386,$(DEB_HOST_ARCH))
+ sed -i 's/TIME_T_MAX_BITS 32/TIME_T_MAX_BITS 31/' config.h
+endif
+
+ $(MAKE) dovecot-config
+# remove unreproducible '-ffile-prefix-map=/build/1st/dovecot-2.3.10.1+dfsg1=.'
+# '-fdebug-prefix-map=/build/dovecot-O55xqn/dovecot-2.3.10.1+dfsg1=.'
+ sed -i 's/\s\+-ffile-prefix-map=\S\+=.\s\+/ /g' dovecot-config
+ sed -i 's/\s\+-fdebug-prefix-map=\S\+=.\s\+/ /g' dovecot-config
+
+# Pigeonhole
+ touch $(PIGEONHOLE_DIR)/stamp.h.in
+ dh_auto_configure -D $(PIGEONHOLE_DIR) -- \
+ --with-dovecot=../ \
+ --libdir=\$${prefix}/lib \
+ --libexecdir=\$${prefix}/lib \
+ --disable-static \
+ --with-ldap=plugin
+
+override_dh_auto_build:
+# Remove all-settings.c and let it be re-generated from the (patched)
+# component settings files.
+ rm -f src/config/all-settings.c
+# Create doc/stamp-man, which may have been removed on an earlier
+# 'clean' invocation, so we don't try to download docs content from
+# the internet:
+ touch doc/stamp-man
+ dh_auto_build
+ dh_auto_build -D $(PIGEONHOLE_DIR)
+
+override_dh_auto_clean:
+ dh_auto_clean
+ dh_auto_clean -D $(PIGEONHOLE_DIR)
+ -rm -f $(CLEANFILES)
+
+override_dh_auto_install:
+# Install everything under dovecot-core
+ $(MAKE) install DESTDIR=$(CORE_DIR)
+ $(MAKE) -C $(PIGEONHOLE_DIR) install DESTDIR=$(CORE_DIR)
+ rm `find $(CURDIR)/debian -name '*.la'`
+
+override_dh_install:
+# dh_auto_install has installed everything in the dovecot-core package.
+# Run dh_install to install additional files from the source directory
+# or debian/ to the individual packages. NOTE: there is nothing in
+# debian/tmp!
+ dh_install
+ install -D -m 0755 $(CORE_DIR)/usr/share/doc/dovecot-core/mkcert.sh \
+ $(CORE_DIR)/usr/share/dovecot/mkcert.sh
+ rm $(CORE_DIR)/usr/share/doc/dovecot-core/mkcert.sh
+ rm $(CORE_DIR)/usr/share/doc/dovecot-core/dovecot-openssl.cnf
+ rm $(CORE_DIR)/usr/share/doc/dovecot-core/solr-*.xml
+
+# We want the dovecot.conf managed by ucf, not the upstream default:
+ rm -f $(CORE_DIR)/etc/dovecot/dovecot.conf
+ cp -r debian/conf/* $(CORE_DIR)/usr/share/dovecot/
+ $(foreach package,$(patsubst dovecot-%,%,$(shell dh_listpackages)),\
+ $(foreach file,$($(package)_files),\
+ $(call MOVE,$(file),$(package))))
+
+ rmdir $(CORE_DIR)/usr/include
+ rmdir $(CORE_DIR)/usr/share/aclocal
+ install -d $(PKG_DIR:PKG=dev)/usr/share/dovecot
+ grep ABI_VERSION config.h | awk '{print $$NF}' | tr -d \" \
+ | tr '[A-Z]' '[a-z]' > $(PKG_DIR:PKG=dev)/usr/share/dovecot/dovecot-abi
+# sanity check the ABI:
+ grep -F -q abi $(PKG_DIR:PKG=dev)/usr/share/dovecot/dovecot-abi
+
+# Install apport hook
+ install -D -m 644 debian/source_dovecot.py $(CURDIR)/debian/dovecot-core/usr/share/apport/package-hooks/dovecot-core.py
+
+ install -D -m644 debian/dovecot-imapd.ufw.profile debian/dovecot-imapd/etc/ufw/applications.d/dovecot-imapd
+ install -D -m644 debian/dovecot-pop3d.ufw.profile debian/dovecot-pop3d/etc/ufw/applications.d/dovecot-pop3d
+
+override_dh_installpam:
+ dh_installpam -pdovecot-core --name=dovecot
+
+override_dh_installinit:
+ dh_installinit -pdovecot-core --name=dovecot -u"defaults 20"
+
+override_dh_installsystemd:
+ dh_installsystemd -pdovecot-core dovecot.service
+ dh_installsystemd -pdovecot-core --no-enable dovecot.socket
+
+override_dh_gencontrol:
+ dh_gencontrol -- -Vdovecot:ABI-Version=$(shell cat $(CURDIR)/debian/dovecot-dev/usr/share/dovecot/dovecot-abi)
+
+override_dh_makeshlibs:
+# Do not add an ldconfig trigger; none of the dovecot shared libraries
+# are public.
+ dh_makeshlibs -n
+
+override_dh_installdocs:
+ dh_installdocs
+ cp pigeonhole/README $(CORE_DIR)/usr/share/doc/dovecot-core/pigeonhole.README
+ cp pigeonhole/NEWS $(CORE_DIR)/usr/share/doc/dovecot-core/pigeonhole.NEWS
--- /dev/null
+---
+include:
+ - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml
+
+# The test suite does not pass reprotest
+variables:
+ SALSA_CI_DISABLE_REPROTEST: 1
+ SALSA_CI_DISABLE_CROSSBUILD_ARM64: 1
+ SALSA_CI_DISABLE_BUILD_PACKAGE_TWICE: 1
--- /dev/null
+3.0 (quilt)
--- /dev/null
+dovecot source: intra-source-package-circular-dependency dovecot-core dovecot-sieve
--- /dev/null
+#!/usr/bin/python3
+
+'''Dovecot Apport interface
+
+Copyright (C) 2010 Canonical Ltd/
+Author: Chuck Short <chuck.short@canonical.com>
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version. See http://www.gnu.org/copyleft/gpl.html for
+the full text of the license.
+'''
+
+from apport.hookutils import *
+
+def add_info(report, ui):
+ response = ui.yesno("The output of dovecot -n may help developers diagnose your bug more quickly, however, it may contain sensitive information. Do you want to include it in your bug report?")
+
+ if response is None: #user canceled
+ raise StopIteration
+
+ elif response:
+ report['DovecotConf'] = root_command_output(['/usr/sbin/dovecot', '-n'])
+
+
+ elif not response:
+ ui.information("The contents of dovecot -n will NOT be includeded in the bug report")
+
+ packages = ['dovecot-common', 'dovecot-core', 'dovecot-dev', 'dovecot-pop3d', 'dovecot-imapd', 'mail-stack-delivery', 'dovecot-postfix']
+ versions = ''
+ for package in packages:
+ try:
+ version = package.get_version(package)
+ except:
+ version = 'N/A'
+ versions += '%s %s\n' %(package, version)
+ report['DovecotInstalledVersions'] = versions
--- /dev/null
+Tests: doveadm
+Depends: dovecot-core
+Restrictions: needs-root
+
+Tests: systemd
+Depends: dovecot-core, systemd-sysv
+
+Test-Command: run-parts --report --exit-on-error debian/tests/usage
+Depends: dovecot-imapd, dovecot-pop3d, dovecot-managesieved, sieve-connect, python3
+Restrictions: needs-root, breaks-testbed, allow-stderr
+
+Tests: testmails
+Restrictions: needs-root, breaks-testbed
+Depends: dovecot-imapd, dovecot-pop3d, lsb-release, python3, python3-passlib
--- /dev/null
+#!/bin/sh
+
+set -e
+
+echo "Checking if dovecot is running"
+doveadm -f flow instance list | grep -q 'running=yes'
+echo "OK"
--- /dev/null
+#!/bin/sh
+
+set -e
+
+echo "Checking whether dovecot.service is enabled"
+systemctl is-enabled dovecot.service
+
+echo "Checking whether dovecot.service is a native unit"
+source=$(systemctl show -pSourcePath dovecot.service | cut -d = -f 2)
+if [ -n "$source" ]; then
+ echo $source
+ exit 1
+else
+ echo "OK (no SourcePath found)"
+fi
+
+echo "Checking whether dovecot.socket is inactive"
+status=$(systemctl show -pActiveState dovecot.socket | cut -d = -f 2)
+echo $status
+if [ "$status" != inactive ]; then
+ exit 1
+fi
--- /dev/null
+#!/usr/bin/python3
+
+import grp
+import imaplib
+import os
+import os.path
+import poplib
+import pwd
+import random
+import string
+import subprocess
+import sys
+import unittest
+
+from passlib.hash import des_crypt
+
+
+def random_string(length):
+ '''Return a random string, consisting of ASCII letters, with given
+ length.'''
+
+ s = ''
+ maxind = len(string.ascii_letters)-1
+ for _ in range(length):
+ s += string.ascii_letters[random.randint(0, maxind)]
+ return s.lower()
+
+
+def login_exists(login):
+ '''Checks whether the given login exists on the system.'''
+
+ try:
+ pwd.getpwnam(login)
+ return True
+ except KeyError:
+ return False
+
+
+def get_distribution():
+ '''Return the name of the Linux Distribution we are running.'''
+ cmd = ['lsb_release', '-si']
+ output = subprocess.check_output(cmd)
+ return output.strip()
+
+
+class TestUser:
+ '''Create a temporary test user and remove it again on close.'''
+
+ def __init__(self):
+ '''Create a new user account with a random password.'''
+
+ self.login = None
+
+ while True:
+ login = random_string(8)
+ if not login_exists(login):
+ break
+
+ self.salt = random_string(2)
+ self.password = random_string(8)
+ self.crypted = des_crypt.using(salt=self.salt).hash(self.password)
+
+ subprocess.check_call(['useradd', '-p', self.crypted, '-m', login])
+
+ self.login = login
+ p = pwd.getpwnam(self.login)
+ self.uid = p[2]
+ self.gid = p[3]
+
+ def __del__(self):
+ '''Remove the created user account.'''
+
+ if self.login:
+ self.close()
+
+ def close(self):
+ '''Remove the created user account.'''
+
+ subprocess.check_call(['userdel', '-f', '-r', self.login])
+ self.login = None
+
+
+class DovecotBasics(unittest.TestCase):
+ '''Base operational tests for Dovecot server.'''
+
+ def setUp(self):
+ '''Create test scenario.
+
+ We want to test the default setup, but pre-setup an mbox on a tmp user
+ '''
+
+ self.distribution = get_distribution()
+ self.user = TestUser()
+
+ # create fresh test mailbox with one new and one old mail
+ self.mailbox = '/var/mail/' + self.user.login
+ self.orig_mbox = '''From test1@test1.com Fri Nov 17 02:21:08 2006
+Date: Thu, 16 Nov 2006 17:12:23 -0800
+From: Test User 1 <test1@test1.com>
+To: Dovecot tester <dovecot@test.com>
+Subject: Test 1
+Status: N
+
+Some really important news.
+
+From test2@test1.com Tue Nov 28 11:29:34 2006
+Date: Tue, 28 Nov 2006 11:29:34 +0100
+From: Test User 2 <test2@test2.com>
+To: Dovecot tester <dovecot@test.com>
+Subject: Test 2
+Status: R
+
+More news.
+
+Get cracking!
+'''
+ with open(self.mailbox, 'w') as f:
+ f.write(self.orig_mbox)
+ os.chown(self.mailbox, self.user.uid, grp.getgrnam('mail')[2])
+ os.chmod(self.mailbox, 0o660)
+
+ def tearDown(self):
+ self.user.close()
+
+ def _test_pop3_proto(self, pop):
+ '''Internal factorization of POP3 protocol checks with an established
+ connection.'''
+
+ # check empty password
+ self.assertEqual(pop.user(self.user.login), b'+OK')
+ self.assertRaises(poplib.error_proto, pop.pass_, '')
+
+ # check wrong password
+ self.assertEqual(pop.user(self.user.login), b'+OK')
+ self.assertRaises(poplib.error_proto, pop.pass_, '123')
+
+ # check correct password
+ self.assertEqual(pop.user(self.user.login), b'+OK')
+ self.assertEqual(pop.pass_(self.user.password), b'+OK Logged in.')
+
+ # check messages
+ self.assertEqual(pop.stat()[0], 2, b'2 available messages')
+ self.assertEqual(pop.list()[1], [b'1 163', b'2 161'])
+ self.assertEqual('\n'.join(l.decode() for l in pop.retr(1)[1]), '''Date: Thu, 16 Nov 2006 17:12:23 -0800
+From: Test User 1 <test1@test1.com>
+To: Dovecot tester <dovecot@test.com>
+Subject: Test 1
+
+Some really important news.''')
+ self.assertEqual('\n'.join(l.decode() for l in pop.retr(2)[1]), '''Date: Tue, 28 Nov 2006 11:29:34 +0100
+From: Test User 2 <test2@test2.com>
+To: Dovecot tester <dovecot@test.com>
+Subject: Test 2
+
+More news.
+
+Get cracking!''')
+
+ self.assertEqual(pop.quit(), b'+OK Logging out.')
+
+ def test_pop3(self):
+ '''Test POP3 protocol.'''
+
+ pop = poplib.POP3('localhost')
+ self.assertEqual(pop.getwelcome(), b'+OK Dovecot ready.')
+
+ self._test_pop3_proto(pop)
+
+ def test_pop3s(self):
+ '''Test POP3S protocol.'''
+
+ pop = poplib.POP3_SSL('localhost')
+ self.assertEqual(pop.getwelcome(), b'+OK Dovecot ready.')
+
+ self._test_pop3_proto(pop)
+
+ def _test_imap_proto(self, imap):
+ '''Internal factorization of IMAP4 protocol checks with an established
+ connection.'''
+
+ # invalid passwords
+ self.assertRaises(imaplib.IMAP4.error, imap.login,
+ self.user.login, '')
+ self.assertRaises(imaplib.IMAP4.error, imap.login,
+ self.user.login, '123')
+
+ # correct password
+ imap.login(self.user.login, self.user.password)
+
+ # list mailboxes
+ status, imlist = imap.list()
+ self.assertEqual(status, 'OK')
+ self.assertTrue(imlist[0].decode().endswith('INBOX'))
+
+ # check mails
+ imap.select()
+ self.assertEqual(imap.search(None, 'ALL'), ('OK', [b'1 2']))
+ self.assertEqual(imap.fetch('1', '(FLAGS)'),
+ ('OK', [b'1 (FLAGS (\\Recent))']))
+ self.assertEqual(imap.fetch('2', '(FLAGS)'),
+ ('OK', [b'2 (FLAGS (\\Seen \\Recent))']))
+ self.assertEqual(imap.fetch('1', '(BODY[TEXT])')[1][0][1],
+ b'Some really important news.\r\n')
+ self.assertEqual(imap.fetch('2', '(BODY[TEXT])')[1][0][1],
+ b'More news.\r\n\r\nGet cracking!')
+
+ self.assertEqual(imap.fetch('1', '(RFC822)')[1],
+ [(b'1 (RFC822 {163}',
+ b'''Date: Thu, 16 Nov 2006 17:12:23 -0800\r
+From: Test User 1 <test1@test1.com>\r
+To: Dovecot tester <dovecot@test.com>\r
+Subject: Test 1\r
+\r
+Some really important news.\r
+'''), b')'])
+
+ # delete mail 1
+ self.assertEqual(imap.store('1', '+FLAGS', '\\Deleted')[0], 'OK')
+ self.assertEqual(imap.expunge()[0], 'OK')
+ self.assertEqual(imap.search(None, 'ALL'), ('OK', [b'1']))
+
+ # old mail 2 is mail 1 now
+ self.assertEqual(imap.fetch('1', '(RFC822)')[1],
+ [(b'1 (RFC822 {161}',
+ b'''Date: Tue, 28 Nov 2006 11:29:34 +0100\r
+From: Test User 2 <test2@test2.com>\r
+To: Dovecot tester <dovecot@test.com>\r
+Subject: Test 2\r
+\r
+More news.\r
+\r
+Get cracking!'''), b')'])
+ imap.close()
+ imap.logout()
+
+ def test_imap(self):
+ '''Test IMAP4 protocol.'''
+
+ imap = imaplib.IMAP4('localhost')
+ self._test_imap_proto(imap)
+
+ def test_imaps(self):
+ '''Test IMAP4S protocol.'''
+
+ imap = imaplib.IMAP4_SSL('localhost')
+ self._test_imap_proto(imap)
+
+
+if __name__ == '__main__':
+ os.dup2(1, 2)
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(DovecotBasics))
+ result = unittest.TextTestRunner(verbosity=2).run(suite)
+ sys.exit(not result.wasSuccessful())
--- /dev/null
+#!/bin/sh
+
+set -e
+
+echo "Setting up dovecot for the test"
+# Move aside 10-auth.conf to disable passwd-based auth
+if [ -f /etc/dovecot/conf.d/10-auth.conf ]; then
+ mv /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.bak
+fi
+
+cat >/etc/dovecot/local.conf <<-EOF
+mail_driver = maildir
+mail_path = ~/Maildir
+mail_inbox_path = ~/Maildir
+
+auth_mechanisms = plain
+
+passdb static {
+ driver = static
+ passdb_static_password = test
+}
+
+userdb static {
+ driver = static
+ fields {
+ uid=nobody
+ gid=nogroup
+ home=/srv/dovecot-dep8/%u
+ }
+}
+EOF
+
+mkdir -p /srv/dovecot-dep8
+chown nobody:nogroup /srv/dovecot-dep8
+
+echo "Restarting the service"
+systemctl restart dovecot
+
+echo "Sending a test message via the LDA"
+/usr/lib/dovecot/dovecot-lda -f "test@example.com" -d dep8 <<EOF
+Return-Path: <test@example.com>
+Message-Id: <dep8-test-1@debian.org>
+From: Test User <test@example.com>
+To: dep8 <dep8@example.com>
+Subject: DEP-8 test
+
+This is just a test
+EOF
+
+echo "Verifying that the email was correctly delivered"
+if [ -z "$(doveadm search -u dep8 header message-id dep8-test-1@debian.org)" ]; then
+ echo "Message not found"
+ exit 1
+fi
+
+echo "Done"
+echo
--- /dev/null
+#!/usr/bin/python3
+import imaplib
+
+imaplib.Debug = 4
+
+print("Testing IMAP")
+print("Connecting")
+client = imaplib.IMAP4('localhost')
+
+print("Checking for STARTTLS capability")
+assert 'STARTTLS' in client.capabilities
+
+client.starttls()
+
+print("Logging in")
+client.login('dep8', 'test')
+
+print("Selecting INBOX")
+client.select()
+
+print("Looking for the test message")
+res, uids = client.search(None, 'HEADER', 'MESSAGE-ID', '"<dep8-test-1@debian.org>"')
+
+assert res == 'OK'
+assert len(uids[0]) > 0
+
+uid = uids[0].split()[0]
+
+print("Fetching and verifying test message")
+res, data = client.fetch(uid, '(RFC822)')
+
+assert res == 'OK'
+
+lines = data[0][1].splitlines()
+
+assert b'Subject: DEP-8 test' in lines
+
+print("Done")
--- /dev/null
+#!/usr/bin/python3
+import poplib
+
+print("Testing POP3")
+print("Connecting")
+client = poplib.POP3('localhost')
+client.set_debuglevel(2)
+
+print("Checking for STARTTLS capability")
+assert 'STLS' in client.capa()
+
+client.stls()
+
+print("Logging in")
+client.user('dep8')
+client.pass_('test')
+
+print("Listing INBOX")
+res, data, _ = client.list()
+assert res.startswith(b'+OK')
+
+print("Fetching and verifying test message")
+for entry in data:
+ _id, _ = entry.split(maxsplit=1)
+ res, body, _ = client.retr(int(_id))
+ if b'Subject: DEP-8 test' in body:
+ break
+else:
+ raise AssertionError("Test message not found")
+
+print("Done")
--- /dev/null
+#!/usr/bin/bash
+# Validate managesieved authentication
+
+user=dep8
+pass=test
+
+sieve-connect --notlsverify --debug -s localhost \
+ -p 4190 -u "$user" --list --passwordfd 3 3< <(echo "$pass")
--- /dev/null
+Name: dovecot
+Repository: https://github.com/dovecot/core
+Repository-Browse: https://github.com/dovecot/core
+Contact: Dovecot Mailing List <dovecot at dovecot.org>
+Bug-Submit: Dovecot Mailing List <dovecot at dovecot.org>
--- /dev/null
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEZ49ZxhYJKwYBBAHaRw8BAQdABhVJFqsoq7gZeXhY/xcz5Vc0i+vM/Df8RDSW
+lzPHsoC0MkRvdmVjb3QgQ29tbXVuaXR5IEVkaXRpb24gPGRvdmVjb3QtY2VAZG92
+ZWNvdC5vcmc+iJAEExYIADgWIQTvCIIHn9TtMr+LI7KhsJ74TtxSGQUCZ49ZxgIb
+AwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRChsJ74TtxSGUgvAP9gNC6pZKBV
+micPYvE2UOTvX8WoEcjWI1UptTxFsHXOjwEA6DZiPoPn9Vvl4PuyG8TavjDslOL8
+pfn+XKZho2WoAgSJAjMEEAEKAB0WIQQr50qrPudU37nIDTMYo0iu7UCdoQUCZ49a
+xgAKCRAYo0iu7UCdoVPOD/452ZnhEYW2/VxgDmD7DNaaxdfL/vKifc9MP2sJm+a2
+T95zHpm/erV2xw3gPpKcLuMaPhkexf6MMhoh9GnUnwEuoOfMWhMAn26RNPFvuUL0
+a73qoxRY9D+Q/PUNjNsNFj/3c8VIC3DeQhO77wVblLWr9+7J9GrzKtBCkb7zC5Xf
+8zjLkCWZ15S0kVKNvBsZzEzLYVN/+Kk1s1WP4x1gnQdPm8N7+b4XAQSOUXLjkL5S
+vGyIUuHwnYHQJeH51L+q8SpzEr59K9OiVYOCm0ICl28xjOjIC9Ym7a8W5Xa5new3
+ScxNeTuxRItYeDyxXi+YIYGHWYGjmppmlq3VksXirUHfgNoipVSbmGxCukShSsfH
+f/40iO8DRFRnsUN2lzN4TCcT1ayQvKoyMFuoXQJonuLdbc7E+YLNIPsW66+aoAuW
+g+5j2QFMFxAJS9IoTCsVQv52MuCj2X5KgwmBcYSgWGpoog9Yx+aaVQQ+X++9FX8r
+RcnyOGOBaA6htH4g1kUOWwZ/NxObRiA7QLo4Rz6CJrU7ueRIaeSCCXTxCb5dJDeE
+IB1MZh1artKZ6Rko+cv/Vy0Ojb0JfPDDoQyhblPIXhlJN8/4zFCjxilMDA9CTAB6
+kY1TVGp138CDnJHZvCdvbrlWf7ktw3+CGKQsZq96Jf756L28dTA42E+WqCBFMZna
+V7g4BGePWcYSCisGAQQBl1UBBQEBB0A50U//EHUvujYRMNqQ3XMVt8lxU7HtdmsH
+Bamh6BjmLgMBCAeIeAQYFggAIBYhBO8Iggef1O0yv4sjsqGwnvhO3FIZBQJnj1nG
+AhsMAAoJEKGwnvhO3FIZEZQBAKuMI//Cjl38pD68fQfcnIMDV2YnXjvaswe7jbDr
+Jyo4AQDr/VanhFwPxSlZ3MKY4PWyLw4Fm9mTT2ofcluFttRsDg==
+=eQ4x
+-----END PGP PUBLIC KEY BLOCK-----
--- /dev/null
+# watch control file for uscan
+version=4
+# core
+opts="pgpmode=auto, dversionmangle=s/\+dfsg\d*//, uversionmangle=s/\.-[0-9]+//, oversionmangle=s/(.*)/$1+dfsg1/" \
+ https://dovecot.org/releases/2.4/dovecot-([\.0-9]+)(-[0-9]+)?\.tar\.gz debian
+# pigeonhole
+opts="pgpmode=auto, dversionmangle=s/\+dfsg\d*//, uversionmangle=s/\.-[0-9]//, oversionmangle=s/(.*)/$1+dfsg1/, component=pigeonhole" \
+ https://pigeonhole.dovecot.org/releases/2.4/dovecot-pigeonhole-([\.0-9]+)(-[0-9]+)?\.tar\.gz ignore uupdate